├── .gitignore ├── LICENSE ├── README.md ├── bundle ├── cgx-legacy.bat ├── cgx-legacy.sh ├── cgx-lite.bat ├── cgx-lite.sh ├── cgx.bat ├── cgx.sh ├── colors.csv └── survivors │ ├── bimp1 │ ├── bimp2 │ ├── shooterA1 │ ├── shooterA2 │ ├── shooterB │ └── shooterC ├── pom.xml └── src └── main └── java └── il └── co └── codeguru └── corewars8086 ├── CoreWarsEngine.java ├── cli ├── HeadlessCompetitionRunner.java └── Options.java ├── cpu ├── Cpu.java ├── CpuException.java ├── CpuState.java ├── DivisionException.java ├── IndirectAddressingDecoder.java ├── IntOpcodeException.java ├── InvalidOpcodeException.java ├── OpcodeFetcher.java ├── RegisterIndexingDecoder.java ├── UnimplementedOpcodeException.java └── UnsupportedOpcodeException.java ├── gui ├── Canvas.java ├── ColorHolder.java ├── ColumnGraph.java ├── CompetitionWindow.java ├── CpuFrame.java ├── FlagFields.java ├── MemoryFrame.java ├── MouseAddressRequest.java ├── RegisterField.java ├── TeamColorHolder.java └── WarFrame.java ├── memory ├── AbstractRealModeMemory.java ├── MemoryEventListener.java ├── MemoryException.java ├── RealModeAddress.java ├── RealModeMemory.java ├── RealModeMemoryImpl.java ├── RealModeMemoryRegion.java └── RestrictedAccessRealModeMemory.java ├── utils ├── Disassembler.java ├── EventMulticaster.java └── Unsigned.java └── war ├── Competition.java ├── CompetitionEventListener.java ├── CompetitionIterator.java ├── ScoreEventListener.java ├── War.java ├── Warrior.java ├── WarriorData.java ├── WarriorFilenameFilter.java ├── WarriorGroup.java ├── WarriorRepository.java └── WarriorType.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.iml 3 | .idea/** 4 | survivors/ 5 | zombies/ 6 | *.csv 7 | !bundle/** 8 | !bundle/*.csv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | corewars8086 2 | ============ 3 | 4 | Core Wars for standard 8086 assembly. 5 | -------------------------------------------------------------------------------- /bundle/cgx-legacy.bat: -------------------------------------------------------------------------------- 1 | @java -jar bin\corewars8086-5.0.1.jar --noparallel --zombieSpeed 1 -------------------------------------------------------------------------------- /bundle/cgx-legacy.sh: -------------------------------------------------------------------------------- 1 | java -jar bin/corewars8086-5.0.1.jar --noparallel --zombieSpeed 2 -------------------------------------------------------------------------------- /bundle/cgx-lite.bat: -------------------------------------------------------------------------------- 1 | @java -jar bin\corewars8086-5.0.1.jar --noparallel -------------------------------------------------------------------------------- /bundle/cgx-lite.sh: -------------------------------------------------------------------------------- 1 | java -jar bin/corewars8086-5.0.1.jar --noparallel 2 | -------------------------------------------------------------------------------- /bundle/cgx.bat: -------------------------------------------------------------------------------- 1 | @java -jar bin\corewars8086-5.0.1.jar -------------------------------------------------------------------------------- /bundle/cgx.sh: -------------------------------------------------------------------------------- 1 | java -jar bin/corewars8086-5.0.1.jar 2 | -------------------------------------------------------------------------------- /bundle/colors.csv: -------------------------------------------------------------------------------- 1 | GSA,#00B000 -------------------------------------------------------------------------------- /bundle/survivors/bimp1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/bimp1 -------------------------------------------------------------------------------- /bundle/survivors/bimp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/bimp2 -------------------------------------------------------------------------------- /bundle/survivors/shooterA1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/shooterA1 -------------------------------------------------------------------------------- /bundle/survivors/shooterA2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/shooterA2 -------------------------------------------------------------------------------- /bundle/survivors/shooterB: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/shooterB -------------------------------------------------------------------------------- /bundle/survivors/shooterC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguru-il/corewars8086/999071591366e6943780943a077b63b2b7ff6242/bundle/survivors/shooterC -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | il.co.codeguru 6 | corewars8086 7 | 5.1.0-SNAPSHOT 8 | jar 9 | ${project_url}/${project.artifactId} 10 | Core Wars for standard 8086 assembly 11 | 12 | 13 | 14 | Danny Leshem 15 | dleshem@gmail.com 16 | 17 | owner 18 | 19 | 20 | 21 | 22 | 23 | 24 | org.apache.commons 25 | commons-math3 26 | 3.6.1 27 | 28 | 29 | com.google.guava 30 | guava 31 | 31.1-jre 32 | 33 | 34 | com.github.pcj 35 | google-options 36 | 1.0.0 37 | 38 | 39 | me.tongfei 40 | progressbar 41 | 0.9.5 42 | 43 | 44 | 45 | 46 | 47 | 48 | maven-compiler-plugin 49 | 2.3.2 50 | 51 | 1.8 52 | 1.8 53 | 54 | 55 | 56 | maven-assembly-plugin 57 | 58 | 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | il.co.codeguru.corewars8086.CoreWarsEngine 70 | 71 | 72 | 73 | 74 | jar-with-dependencies 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/CoreWarsEngine.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086; 2 | 3 | import com.google.devtools.common.options.OptionsParser; 4 | import il.co.codeguru.corewars8086.cli.HeadlessCompetitionRunner; 5 | import il.co.codeguru.corewars8086.cli.Options; 6 | import il.co.codeguru.corewars8086.gui.CompetitionWindow; 7 | 8 | import java.io.IOException; 9 | 10 | public class CoreWarsEngine { 11 | public static void main(String[] args) throws IOException { 12 | OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class); 13 | optionsParser.parseAndExitUponError(args); 14 | Options options = optionsParser.getOptions(Options.class); 15 | 16 | if (options != null && options.headless) { // options should never be null 17 | HeadlessCompetitionRunner r = new HeadlessCompetitionRunner(options); 18 | } else { 19 | CompetitionWindow c = new CompetitionWindow(options); 20 | c.setVisible(true); 21 | c.pack(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cli/HeadlessCompetitionRunner.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cli; 2 | 3 | import com.google.common.primitives.Longs; 4 | import il.co.codeguru.corewars8086.war.Competition; 5 | import il.co.codeguru.corewars8086.war.CompetitionEventListener; 6 | import il.co.codeguru.corewars8086.war.ScoreEventListener; 7 | import il.co.codeguru.corewars8086.war.WarriorRepository; 8 | import me.tongfei.progressbar.ProgressBar; 9 | import me.tongfei.progressbar.ProgressBarBuilder; 10 | import me.tongfei.progressbar.ProgressBarStyle; 11 | 12 | import java.io.IOException; 13 | import java.util.Arrays; 14 | 15 | /** 16 | * @author RM 17 | */ 18 | public class HeadlessCompetitionRunner implements ScoreEventListener, CompetitionEventListener { 19 | private final Options options; 20 | 21 | private int warCounter; 22 | private int totalWars; 23 | 24 | private final Competition competition; 25 | private long seed; 26 | 27 | private Thread warThread; 28 | 29 | private ProgressBar progressBar; 30 | 31 | public HeadlessCompetitionRunner(Options options) throws IOException { 32 | this.options = options; 33 | System.out.println("CoreWars8086 - headless mode\n"); 34 | 35 | this.competition = new Competition(options); 36 | this.competition.addCompetitionEventListener(this); 37 | WarriorRepository repository = competition.getWarriorRepository(); 38 | System.out.printf("Loaded warriors: %s%n", Arrays.toString(repository.getGroupNames())); 39 | repository.addScoreEventListener(this); 40 | 41 | if (Longs.tryParse(options.seed) != null) this.seed = Long.parseLong(options.seed); 42 | else this.seed = options.seed.hashCode(); 43 | 44 | this.warCounter = 0; 45 | this.totalWars = 0; 46 | this.runWar(); 47 | } 48 | 49 | public boolean runWar() { 50 | try { 51 | competition.setSeed(this.seed); 52 | if (competition.getWarriorRepository().getNumberOfGroups() < options.combinationSize) { 53 | System.err.printf("Not enough survivors (got %d but %d are needed)%n", competition.getWarriorRepository().getNumberOfGroups(), options.combinationSize); 54 | return false; 55 | } 56 | 57 | warThread = new Thread("CompetitionThread") { 58 | @Override 59 | public void run() { 60 | try { 61 | if (options.parallel) { 62 | competition.runCompetitionInParallel(options.battlesPerCombo, options.combinationSize, options.threads); 63 | } else { 64 | competition.runCompetition(options.battlesPerCombo, options.combinationSize, false); 65 | } 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | }; 71 | 72 | warThread.start(); 73 | return true; 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | return false; 77 | } 78 | } 79 | 80 | @Override 81 | public void onWarStart(long seed) { 82 | 83 | } 84 | 85 | @Override 86 | public void onWarEnd(int reason, String winners) { 87 | warCounter++; 88 | progressBar.stepTo(warCounter); 89 | } 90 | 91 | @Override 92 | public void onRound(int round) { 93 | 94 | } 95 | 96 | @Override 97 | public void onWarriorBirth(String warriorName) { 98 | 99 | } 100 | 101 | @Override 102 | public void onWarriorDeath(String warriorName, String reason) { 103 | 104 | } 105 | 106 | @Override 107 | public void onCompetitionStart() { 108 | warCounter = 0; 109 | totalWars = competition.getTotalNumberOfWars(); 110 | competition.setAbort(false); 111 | System.out.printf("Starting competition (%d wars)%s.%n", totalWars, options.parallel ? " in parallel" : ""); 112 | progressBar = new ProgressBarBuilder() 113 | .setTaskName("Running wars") 114 | .setStyle(ProgressBarStyle.ASCII) 115 | .setInitialMax(totalWars) 116 | .showSpeed() 117 | .build(); 118 | } 119 | 120 | @Override 121 | public void onCompetitionEnd() { 122 | progressBar.close(); 123 | System.out.printf("Competition is over. Ran %d wars%n", warCounter); 124 | warThread = null; 125 | } 126 | 127 | @Override 128 | public void onEndRound() { 129 | 130 | } 131 | 132 | @Override 133 | public void scoreChanged(String name, float addedValue, int groupIndex, int subIndex) { 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cli/Options.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cli; 2 | 3 | import com.google.devtools.common.options.Option; 4 | import com.google.devtools.common.options.OptionsBase; 5 | 6 | /** 7 | * Configuration holder for CoreWars8086 - parameters are set using command-line arguments. 8 | *

Due to a limitation with the Google Options library, the parameter fields 9 | * cannot be set as {@code final}, however please treat them so. 10 | * 11 | * @author RM 12 | */ 13 | public class Options extends OptionsBase { 14 | @Option( 15 | name = "headless", 16 | abbrev = 'h', 17 | help = "Run the engine in headless mode", 18 | category = "Startup", 19 | defaultValue = "false" 20 | ) 21 | public boolean headless; 22 | 23 | @Option( 24 | name = "comboSize", 25 | abbrev = 'c', 26 | help = "The size of each group combination", 27 | category = "Gameplay", 28 | defaultValue = "4" 29 | ) 30 | public int combinationSize; 31 | 32 | @Option( 33 | name = "battlesPerCombo", 34 | abbrev = 'b', 35 | help = "Battles per group combination", 36 | category = "Gameplay", 37 | defaultValue = "100" 38 | ) 39 | public int battlesPerCombo; 40 | 41 | @Option( 42 | name = "seed", 43 | abbrev = 's', 44 | help = "Starting seed for the game", 45 | category = "Gameplay", 46 | defaultValue = "guru" 47 | ) 48 | public String seed; 49 | 50 | @Option( 51 | name = "zombieSpeed", 52 | help = "Number of turns zombies play per round", 53 | category = "Gameplay", 54 | defaultValue = "2" 55 | ) 56 | public int zombieSpeed; 57 | 58 | @Option( 59 | name = "parallel", 60 | abbrev = 'p', 61 | help = "Run multiple battles concurrently - cancel for (pre-)cgx2022 result emulation", 62 | category = "Concurrency", 63 | defaultValue = "true" 64 | ) 65 | public boolean parallel; 66 | 67 | @Option( 68 | name = "threads", 69 | abbrev = 't', 70 | help = "Number of threads for parallel mode", 71 | category = "Concurrency", 72 | defaultValue = "4" 73 | ) 74 | public int threads; 75 | 76 | @Option( 77 | name = "warriorsDir", 78 | abbrev = 'w', 79 | help = "Directory for warrior files", 80 | category = "Data", 81 | defaultValue = "survivors" 82 | ) 83 | public String warriorsDir; 84 | 85 | @Option( 86 | name = "zombiesDir", 87 | abbrev = 'z', 88 | help = "Directory for zombie files", 89 | category = "Data", 90 | defaultValue = "zombies" 91 | ) 92 | public String zombiesDir; 93 | 94 | @Option( 95 | name = "outputFile", 96 | abbrev = 'o', 97 | help = "Path to scores output file", 98 | category = "Data", 99 | defaultValue = "scores.csv" 100 | ) 101 | public String outputFile; 102 | 103 | @Option( 104 | name = "colorsFile", 105 | help = "Path to color holder file", 106 | category = "Data", 107 | defaultValue = "colors.csv" 108 | ) 109 | public String colorsFile; 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/CpuException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Base class for all Exceptions thrown by the Cpu class. 5 | * 6 | * @author DL 7 | */ 8 | public abstract class CpuException extends Exception { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/CpuState.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | import il.co.codeguru.corewars8086.utils.Unsigned; 4 | 5 | /** 6 | * Wrapper class for CPU state (registers & flags). 7 | * 8 | * @author DL 9 | */ 10 | public class CpuState { 11 | 12 | /** Accessors for the 16bit registers */ 13 | public short getAX() { 14 | return m_ax; 15 | } 16 | public short getBX() { 17 | return m_bx; 18 | } 19 | public short getCX() { 20 | return m_cx; 21 | } 22 | public short getDX() { 23 | return m_dx; 24 | } 25 | public short getDS() { 26 | return m_ds; 27 | } 28 | public short getES() { 29 | return m_es; 30 | } 31 | public short getSI() { 32 | return m_si; 33 | } 34 | public short getDI() { 35 | return m_di; 36 | } 37 | public short getSS() { 38 | return m_ss; 39 | } 40 | public short getBP() { 41 | return m_bp; 42 | } 43 | public short getSP() { 44 | return m_sp; 45 | } 46 | public short getCS() { 47 | return m_cs; 48 | } 49 | public short getIP() { 50 | return m_ip; 51 | } 52 | public short getFlags() { 53 | return m_flags; 54 | } 55 | 56 | public void setAX(short value) { 57 | m_ax = value; 58 | } 59 | public void setBX(short value) { 60 | m_bx = value; 61 | } 62 | public void setCX(short value) { 63 | m_cx = value; 64 | } 65 | public void setDX(short value) { 66 | m_dx = value; 67 | } 68 | public void setDS(short value) { 69 | m_ds = value; 70 | } 71 | public void setES(short value) { 72 | m_es = value; 73 | } 74 | public void setSI(short value) { 75 | m_si = value; 76 | } 77 | public void setDI(short value) { 78 | m_di = value; 79 | } 80 | public void setSS(short value) { 81 | m_ss = value; 82 | } 83 | public void setBP(short value) { 84 | m_bp = value; 85 | } 86 | public void setSP(short value) { 87 | m_sp = value; 88 | } 89 | public void setCS(short value) { 90 | m_cs = value; 91 | } 92 | public void setIP(short value) { 93 | m_ip = value; 94 | } 95 | public void setFlags(short value) { 96 | m_flags = value; 97 | } 98 | 99 | /** Accessors for the 8bit registers */ 100 | public byte getAL() { 101 | return (byte)m_ax; 102 | } 103 | public byte getBL() { 104 | return (byte)m_bx; 105 | } 106 | public byte getCL() { 107 | return (byte)m_cx; 108 | } 109 | public byte getDL() { 110 | return (byte)m_dx; 111 | } 112 | public byte getAH() { 113 | return (byte)(m_ax >> 8); 114 | } 115 | public byte getBH() { 116 | return (byte)(m_bx >> 8); 117 | } 118 | public byte getCH() { 119 | return (byte)(m_cx >> 8); 120 | } 121 | public byte getDH() { 122 | return (byte)(m_dx >> 8); 123 | } 124 | 125 | public void setAL(byte value) { 126 | m_ax &= 0xFF00; 127 | m_ax |= Unsigned.unsignedByte(value); 128 | } 129 | public void setBL(byte value) { 130 | m_bx &= 0xFF00; 131 | m_bx |= Unsigned.unsignedByte(value); 132 | } 133 | public void setCL(byte value) { 134 | m_cx &= 0xFF00; 135 | m_cx |= Unsigned.unsignedByte(value); 136 | } 137 | public void setDL(byte value) { 138 | m_dx &= 0xFF00; 139 | m_dx |= Unsigned.unsignedByte(value); 140 | } 141 | public void setAH(byte value) { 142 | m_ax &= 0x00FF; 143 | m_ax |= (Unsigned.unsignedByte(value) << 8); 144 | } 145 | public void setBH(byte value) { 146 | m_bx &= 0x00FF; 147 | m_bx |= (Unsigned.unsignedByte(value) << 8); 148 | } 149 | public void setCH(byte value) { 150 | m_cx &= 0x00FF; 151 | m_cx |= (Unsigned.unsignedByte(value) << 8); 152 | } 153 | public void setDH(byte value) { 154 | m_dx &= 0x00FF; 155 | m_dx |= (Unsigned.unsignedByte(value) << 8); 156 | } 157 | 158 | /** Accessors for the virtual Energy register. */ 159 | public short getEnergy() { 160 | return m_energy; 161 | } 162 | public void setEnergy(short value) { 163 | m_energy = value; 164 | } 165 | 166 | /** Accessors for the virtual bomb count registers. */ 167 | public byte getBomb1Count() { 168 | return m_bomb1count; 169 | } 170 | public void setBomb1Count(byte value) { 171 | m_bomb1count = value; 172 | } 173 | public byte getBomb2Count() { 174 | return m_bomb2count; 175 | } 176 | public void setBomb2Count(byte value) { 177 | m_bomb2count = value; 178 | } 179 | 180 | /** 181 | * 'get' accessor methods for the various fields of the flags register. 182 | * @return whether or not the requested flags field is set. 183 | */ 184 | public boolean getCarryFlag() { 185 | return ((m_flags & FLAGS_MASK_CARRY) == FLAGS_MASK_CARRY); 186 | } 187 | public boolean getParityFlag() { 188 | return ((m_flags & FLAGS_MASK_PARITY) == FLAGS_MASK_PARITY); 189 | } 190 | public boolean getAuxFlag() { 191 | return ((m_flags & FLAGS_MASK_AUX) == FLAGS_MASK_AUX); 192 | } 193 | public boolean getZeroFlag() { 194 | return ((m_flags & FLAGS_MASK_ZERO) == FLAGS_MASK_ZERO); 195 | } 196 | public boolean getSignFlag() { 197 | return ((m_flags & FLAGS_MASK_SIGN) == FLAGS_MASK_SIGN); 198 | } 199 | public boolean getTrapFlag() { 200 | return ((m_flags & FLAGS_MASK_TRAP) == FLAGS_MASK_TRAP); 201 | } 202 | public boolean getInterruptFlag() { 203 | return ((m_flags & FLAGS_MASK_INTERRUPT) == FLAGS_MASK_INTERRUPT); 204 | } 205 | public boolean getDirectionFlag() { 206 | return ((m_flags & FLAGS_MASK_DIRECTION) == FLAGS_MASK_DIRECTION); 207 | } 208 | public boolean getOverflowFlag() { 209 | return ((m_flags & FLAGS_MASK_OVERFLOW) == FLAGS_MASK_OVERFLOW); 210 | } 211 | 212 | /** 213 | * 'set' accessor methods for the various fields of the flags register. 214 | * @param newValue whether or not the requested flags field should be set. 215 | */ 216 | public void setCarryFlag(boolean newValue) { 217 | if (newValue) { 218 | m_flags |= FLAGS_MASK_CARRY; 219 | } else { 220 | m_flags &= (~FLAGS_MASK_CARRY); 221 | } 222 | } 223 | public void setParityFlag(boolean newValue) { 224 | if (newValue) { 225 | m_flags |= FLAGS_MASK_PARITY; 226 | } else { 227 | m_flags &= (~FLAGS_MASK_PARITY); 228 | } 229 | } 230 | public void setAuxFlag(boolean newValue) { 231 | if (newValue) { 232 | m_flags |= FLAGS_MASK_AUX; 233 | } else { 234 | m_flags &= (~FLAGS_MASK_AUX); 235 | } 236 | } 237 | public void setZeroFlag(boolean newValue) { 238 | if (newValue) { 239 | m_flags |= FLAGS_MASK_ZERO; 240 | } else { 241 | m_flags &= (~FLAGS_MASK_ZERO); 242 | } 243 | } 244 | public void setSignFlag(boolean newValue) { 245 | if (newValue) { 246 | m_flags |= FLAGS_MASK_SIGN; 247 | } else { 248 | m_flags &= (~FLAGS_MASK_SIGN); 249 | } 250 | } 251 | public void setTrapFlag(boolean newValue) { 252 | if (newValue) { 253 | m_flags |= FLAGS_MASK_TRAP; 254 | } else { 255 | m_flags &= (~FLAGS_MASK_TRAP); 256 | } 257 | } 258 | public void setInterruptFlag(boolean newValue) { 259 | if (newValue) { 260 | m_flags |= FLAGS_MASK_INTERRUPT; 261 | } else { 262 | m_flags &= (~FLAGS_MASK_INTERRUPT); 263 | } 264 | } 265 | public void setDirectionFlag(boolean newValue) { 266 | if (newValue) { 267 | m_flags |= FLAGS_MASK_DIRECTION; 268 | } else { 269 | m_flags &= (~FLAGS_MASK_DIRECTION); 270 | } 271 | } 272 | public void setOverflowFlag(boolean newValue) { 273 | if (newValue) { 274 | m_flags |= FLAGS_MASK_OVERFLOW; 275 | } else { 276 | m_flags &= (~FLAGS_MASK_OVERFLOW); 277 | } 278 | } 279 | 280 | /** CPU registers */ 281 | private short m_ax; 282 | private short m_bx; 283 | private short m_cx; 284 | private short m_dx; 285 | 286 | private short m_ds; 287 | private short m_es; 288 | private short m_si; 289 | private short m_di; 290 | 291 | private short m_ss; 292 | private short m_bp; 293 | private short m_sp; 294 | 295 | private short m_cs; 296 | private short m_ip; 297 | private short m_flags; 298 | 299 | /** The virtual Energy register (used to calculate the warrior's speed). */ 300 | private short m_energy; 301 | 302 | /** The virtual bomb count registers (used for INT 0x86, INT 0x87 opcodes). */ 303 | private byte m_bomb1count; 304 | private byte m_bomb2count; 305 | 306 | 307 | /** 308 | * Masks for the various 'flags' fields. 309 | */ 310 | private static final short FLAGS_MASK_CARRY = 0x0001; 311 | private static final short FLAGS_MASK_PARITY = 0x0004; 312 | private static final short FLAGS_MASK_AUX = 0x0010; 313 | private static final short FLAGS_MASK_ZERO = 0x0040; 314 | private static final short FLAGS_MASK_SIGN = 0x0080; 315 | private static final short FLAGS_MASK_TRAP = 0x0100; 316 | private static final short FLAGS_MASK_INTERRUPT = 0x0200; 317 | private static final short FLAGS_MASK_DIRECTION = 0x0400; 318 | private static final short FLAGS_MASK_OVERFLOW = 0x0800; 319 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/DivisionException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Thrown by DIV/IDIV opcodes when the division overflows, or when dividing by zero. 5 | * 6 | * @author DL 7 | */ 8 | public class DivisionException extends CpuException { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/IndirectAddressingDecoder.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | import il.co.codeguru.corewars8086.memory.MemoryException; 4 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 5 | import il.co.codeguru.corewars8086.memory.RealModeMemory; 6 | 7 | /** 8 | * Decodes indirect-addressing opcodes (translates between the CPU's internal 9 | * representation of indirect-addressing, to the actual real-mode address). 10 | * 11 | * The CPU supports four indirect addressing modes: 12 | * (0) [BX+SI] indirect 13 | * (1) [BX+SI+12h] indirect + imm8 14 | * (2) [BX+SI+1234h] indirect + imm16 15 | * (3) AX direct register mode 16 | * 17 | * Each indirect-addressing opcode has two operands: a register, and one of the 18 | * above. e.g: 19 | * ADD [BX+SI], AX 20 | * 21 | * @author DL 22 | */ 23 | public class IndirectAddressingDecoder { 24 | 25 | /** 26 | * Constructor. 27 | * @param state CPU registers. 28 | * @param memory Memory. 29 | * @param fetcher Used to fetch additional opcode bytes. 30 | */ 31 | public IndirectAddressingDecoder( 32 | CpuState state, RealModeMemory memory, OpcodeFetcher fetcher) { 33 | 34 | m_state = state; 35 | m_memory = memory; 36 | m_fetcher = fetcher; 37 | m_regs = new RegisterIndexingDecoder(m_state); 38 | 39 | m_regIndex = 0; 40 | m_memIndex = 0; 41 | m_memAddress = null; 42 | } 43 | 44 | /** 45 | * Fetches & decodes the bytes currently pointed by the OpcodeFetcher. 46 | * @throws MemoryException on any error while reading from memory. 47 | */ 48 | public void reset() throws MemoryException { 49 | 50 | // read the 'mode' byte (MM RRR III) 51 | // M - indirect addressing mode mux 52 | // R - register indexing 53 | // I - indirect addressing indexing 54 | byte modeByte = m_fetcher.nextByte(); 55 | 56 | byte mode = (byte)((modeByte >> 6) & 0x03); 57 | m_regIndex = (byte)((modeByte >> 3) & 0x07); 58 | m_memIndex = (byte)(modeByte & 0x07); 59 | 60 | // decode the opcode according to the indirect-addressing mode, and 61 | // retrieve the address operand 62 | switch (mode) { 63 | case 0: 64 | m_memAddress = getMode0Address(); 65 | break; 66 | case 1: 67 | m_memAddress = getMode1Address(); 68 | break; 69 | case 2: 70 | m_memAddress = getMode2Address(); 71 | break; 72 | case 3: 73 | m_memAddress = getMode3Address(); 74 | break; 75 | default: 76 | throw new RuntimeException(); 77 | } 78 | } 79 | 80 | /** 81 | * @return 3 bits representing the internal register indexing. 82 | */ 83 | public byte getRegIndex() { 84 | return m_regIndex; 85 | } 86 | 87 | /** 88 | * @return The indirect memory operand's address (or null if the latter 89 | * refers to a register). 90 | */ 91 | public RealModeAddress getMemAddress() { 92 | return m_memAddress; 93 | } 94 | 95 | /** 96 | * Assuming the opcode operand referred to an 8bit register, returns the 97 | * corresponding register's value. 98 | * @return 8bit register value. 99 | */ 100 | public byte getReg8() { 101 | return m_regs.getReg8(m_regIndex); 102 | } 103 | 104 | /** 105 | * Returns the 8bit value pointed by the indirect memory operand (or register, 106 | * depands on the indirect-addressing mode). 107 | * @return Indirect address (or register) 8bit value. 108 | */ 109 | public byte getMem8() throws MemoryException { 110 | if (m_memAddress != null) { 111 | return m_memory.readByte(m_memAddress); 112 | } 113 | return m_regs.getReg8(m_memIndex); 114 | } 115 | 116 | /** 117 | * Assuming the opcode operand referred to a 16bit register, returns the 118 | * corresponding register's value. 119 | * @return 16bit register value. 120 | */ 121 | public short getReg16() { 122 | return m_regs.getReg16(m_regIndex); 123 | } 124 | 125 | /** 126 | * Assuming the opcode operand referred to a segment register, returns the 127 | * corresponding register's value. 128 | * @return segment register value. 129 | */ 130 | public short getSeg() { 131 | return m_regs.getSeg(m_regIndex); 132 | } 133 | 134 | /** 135 | * Returns the 16bit value pointed by the indirect memory operand (or register, 136 | * depands on the indirect-addressing mode). 137 | * @return Indirect address (or register) 16bit value. 138 | */ 139 | public short getMem16() throws MemoryException { 140 | if (m_memAddress != null) { 141 | return m_memory.readWord(m_memAddress); 142 | } 143 | return m_regs.getReg16(m_memIndex); 144 | } 145 | 146 | /** 147 | * Assuming the opcode operand referred to an 8bit register, sets the 148 | * corresponding register's value. 149 | * @param value New value for the 8bit register. 150 | */ 151 | public void setReg8(byte value) { 152 | m_regs.setReg8(m_regIndex, value); 153 | } 154 | 155 | /** 156 | * Sets the 8bit value pointed by the indirect memory operand (or register, 157 | * depands on the indirect-addressing mode). 158 | * @param value Value to set. 159 | */ 160 | public void setMem8(byte value) throws MemoryException { 161 | if (m_memAddress != null) { 162 | m_memory.writeByte(m_memAddress, value); 163 | } else { 164 | m_regs.setReg8(m_memIndex, value); 165 | } 166 | } 167 | 168 | /** 169 | * Assuming the opcode operand referred to a 16bit register, sets the 170 | * corresponding register's value. 171 | * @param value New value for the segment register. 172 | */ 173 | public void setReg16(short value) { 174 | m_regs.setReg16(m_regIndex, value); 175 | } 176 | 177 | /** 178 | * Assuming the opcode operand referred to a segment register, sets the 179 | * corresponding register's value. 180 | * @param value New value for the segment register. 181 | */ 182 | public void setSeg(short value) { 183 | m_regs.setSeg(m_regIndex, value); 184 | } 185 | 186 | /** 187 | * Sets the 16bit value pointed by the indirect memory operand (or register, 188 | * depands on the indirect-addressing mode). 189 | * @param value Value to set. 190 | */ 191 | public void setMem16(short value) throws MemoryException { 192 | if (m_memAddress != null) { 193 | m_memory.writeWord(m_memAddress, value); 194 | } else { 195 | m_regs.setReg16(m_memIndex, value); 196 | } 197 | } 198 | 199 | /** 200 | * Decodes the indirect-memory operand corresponding to mode #0. 201 | * @return the real-mode address to which the indirect-memory operand 202 | * refers to. 203 | * @throws MemoryException on any error while reading from memory. 204 | */ 205 | private RealModeAddress getMode0Address() throws MemoryException { 206 | switch (m_memIndex) { 207 | case 0: 208 | return new RealModeAddress( 209 | m_state.getDS(), (short)(m_state.getBX() + m_state.getSI())); 210 | case 1: 211 | return new RealModeAddress( 212 | m_state.getDS(), (short)(m_state.getBX() + m_state.getDI())); 213 | case 2: 214 | return new RealModeAddress( 215 | m_state.getSS(), (short)(m_state.getBP() + m_state.getSI())); 216 | case 3: 217 | return new RealModeAddress( 218 | m_state.getSS(), (short)(m_state.getBP() + m_state.getDI())); 219 | case 4: 220 | return new RealModeAddress(m_state.getDS(), m_state.getSI()); 221 | case 5: 222 | return new RealModeAddress(m_state.getDS(), m_state.getDI()); 223 | case 6: 224 | return new RealModeAddress(m_state.getDS(), m_fetcher.nextWord()); 225 | case 7: 226 | return new RealModeAddress(m_state.getDS(), m_state.getBX()); 227 | default: 228 | throw new RuntimeException(); 229 | } 230 | } 231 | 232 | /** 233 | * Decodes the indirect-memory operand corresponding to mode #1. 234 | * @return the real-mode address to which the indirect-memory operand 235 | * refers to. 236 | * @throws MemoryException on any error while reading from memory. 237 | */ 238 | private RealModeAddress getMode1Address() throws MemoryException { 239 | switch (m_memIndex) { 240 | case 0: 241 | return new RealModeAddress(m_state.getDS(), 242 | (short)(m_state.getBX() + m_state.getSI() + m_fetcher.nextByte())); 243 | case 1: 244 | return new RealModeAddress(m_state.getDS(), 245 | (short)(m_state.getBX() + m_state.getDI() + m_fetcher.nextByte())); 246 | case 2: 247 | return new RealModeAddress(m_state.getSS(), 248 | (short)(m_state.getBP() + m_state.getSI() + m_fetcher.nextByte())); 249 | case 3: 250 | return new RealModeAddress(m_state.getSS(), 251 | (short)(m_state.getBP() + m_state.getDI() + m_fetcher.nextByte())); 252 | case 4: 253 | return new RealModeAddress(m_state.getDS(), 254 | (short)(m_state.getSI() + m_fetcher.nextByte())); 255 | case 5: 256 | return new RealModeAddress(m_state.getDS(), 257 | (short)(m_state.getDI() + m_fetcher.nextByte())); 258 | case 6: 259 | return new RealModeAddress(m_state.getSS(), 260 | (short)(m_state.getBP() + m_fetcher.nextByte())); 261 | case 7: 262 | return new RealModeAddress(m_state.getDS(), 263 | (short)(m_state.getBX() + m_fetcher.nextByte())); 264 | default: 265 | throw new RuntimeException(); 266 | } 267 | } 268 | 269 | /** 270 | * Decodes the indirect-memory operand corresponding to mode #2. 271 | * @return the real-mode address to which the indirect-memory operand 272 | * refers to. 273 | * @throws MemoryException on any error while reading from memory. 274 | */ 275 | private RealModeAddress getMode2Address() throws MemoryException { 276 | switch (m_memIndex) { 277 | case 0: 278 | return new RealModeAddress(m_state.getDS(), 279 | (short)(m_state.getBX() + m_state.getSI() + m_fetcher.nextWord())); 280 | case 1: 281 | return new RealModeAddress(m_state.getDS(), 282 | (short)(m_state.getBX() + m_state.getDI() + m_fetcher.nextWord())); 283 | case 2: 284 | return new RealModeAddress(m_state.getSS(), 285 | (short)(m_state.getBP() + m_state.getSI() + m_fetcher.nextWord())); 286 | case 3: 287 | return new RealModeAddress(m_state.getSS(), 288 | (short)(m_state.getBP() + m_state.getDI() + m_fetcher.nextWord())); 289 | case 4: 290 | return new RealModeAddress(m_state.getDS(), 291 | (short)(m_state.getSI() + m_fetcher.nextWord())); 292 | case 5: 293 | return new RealModeAddress(m_state.getDS(), 294 | (short)(m_state.getDI() + m_fetcher.nextWord())); 295 | case 6: 296 | return new RealModeAddress(m_state.getSS(), 297 | (short)(m_state.getBP() + m_fetcher.nextWord())); 298 | case 7: 299 | return new RealModeAddress(m_state.getDS(), 300 | (short)(m_state.getBX() + m_fetcher.nextWord())); 301 | default: 302 | throw new RuntimeException(); 303 | } 304 | } 305 | 306 | /** 307 | * Decodes the indirect-memory operand corresponding to mode #3. 308 | * Since in this mode the indirect-memory operand actually referes to one 309 | * of the registers, the method simply returns 'null'. 310 | * @return null (meaning the indirect operand refers to a register). 311 | */ 312 | private RealModeAddress getMode3Address() { 313 | return null; 314 | } 315 | 316 | /** CPU registers */ 317 | private final CpuState m_state; 318 | 319 | /** Memory */ 320 | private final RealModeMemory m_memory; 321 | 322 | /** Used to fetch additional opcode bytes. */ 323 | private final OpcodeFetcher m_fetcher; 324 | 325 | /** Used to decode the non-memory part of the opcode */ 326 | private final RegisterIndexingDecoder m_regs; 327 | 328 | private byte m_regIndex; 329 | private byte m_memIndex; 330 | private RealModeAddress m_memAddress; 331 | } 332 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/IntOpcodeException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Thrown on various interrupt opcodes. 5 | * 6 | * @author DL 7 | */ 8 | public class IntOpcodeException extends CpuException { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/InvalidOpcodeException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Thrown when attempting to execute an invalid opcode. 5 | * 6 | * @author DL 7 | */ 8 | public class InvalidOpcodeException extends CpuException { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/OpcodeFetcher.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | import il.co.codeguru.corewars8086.memory.MemoryException; 4 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 5 | import il.co.codeguru.corewars8086.memory.RealModeMemory; 6 | 7 | /** 8 | * Wraps opcode fetching from CS:IP. 9 | * 10 | * @author DL 11 | */ 12 | public class OpcodeFetcher { 13 | 14 | /** 15 | * Constructor. 16 | * @param state Used to read & update CS:IP. 17 | * @param memory Used to actually read the fetched bytes. 18 | */ 19 | public OpcodeFetcher(CpuState state, RealModeMemory memory) { 20 | m_state = state; 21 | m_memory = memory; 22 | } 23 | 24 | /** 25 | * @return the next byte pointed by CS:IP (and advances IP). 26 | * @throws MemoryException on any error. 27 | */ 28 | public byte nextByte() throws MemoryException { 29 | RealModeAddress address = new RealModeAddress( 30 | m_state.getCS(), m_state.getIP()); 31 | m_state.setIP((short)(m_state.getIP() + 1)); 32 | return m_memory.readExecuteByte(address); 33 | } 34 | 35 | /** 36 | * @return the next word pointed by CS:IP (and advances IP). 37 | * @throws MemoryException on any error. 38 | */ 39 | public short nextWord() throws MemoryException { 40 | RealModeAddress address = new RealModeAddress( 41 | m_state.getCS(), m_state.getIP()); 42 | m_state.setIP((short)(m_state.getIP() + 2)); 43 | return m_memory.readExecuteWord(address); 44 | } 45 | 46 | /** Used to read & update CS:IP. */ 47 | private final CpuState m_state; 48 | 49 | /** Used to actually read the fetched bytes. */ 50 | private final RealModeMemory m_memory; 51 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/RegisterIndexingDecoder.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Decodes the CPU's internal register indexing to the actual registers. 5 | * 6 | * @author DL 7 | */ 8 | public class RegisterIndexingDecoder { 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param state CPU Registers. 14 | */ 15 | RegisterIndexingDecoder(CpuState state) { 16 | m_state = state; 17 | } 18 | 19 | /** 20 | * Returns the value of the 8bit register whose index is given. 21 | * @param index CPU's internal register index. 22 | * @return the value of the 8bit register whose index is given. 23 | */ 24 | public byte getReg8(byte index) { 25 | switch (index) { 26 | case 0: 27 | return m_state.getAL(); 28 | case 1: 29 | return m_state.getCL(); 30 | case 2: 31 | return m_state.getDL(); 32 | case 3: 33 | return m_state.getBL(); 34 | case 4: 35 | return m_state.getAH(); 36 | case 5: 37 | return m_state.getCH(); 38 | case 6: 39 | return m_state.getDH(); 40 | case 7: 41 | return m_state.getBH(); 42 | default: 43 | throw new RuntimeException(); 44 | } 45 | } 46 | 47 | /** 48 | * Sets the value of the 8bit register whose index is given. 49 | * @param index CPU's internal register index. 50 | * @param value New value for above register. 51 | */ 52 | public void setReg8(byte index, byte value) { 53 | switch (index) { 54 | case 0: 55 | m_state.setAL(value); 56 | break; 57 | case 1: 58 | m_state.setCL(value); 59 | break; 60 | case 2: 61 | m_state.setDL(value); 62 | break; 63 | case 3: 64 | m_state.setBL(value); 65 | break; 66 | case 4: 67 | m_state.setAH(value); 68 | break; 69 | case 5: 70 | m_state.setCH(value); 71 | break; 72 | case 6: 73 | m_state.setDH(value); 74 | break; 75 | case 7: 76 | m_state.setBH(value); 77 | break; 78 | default: 79 | throw new RuntimeException(); 80 | } 81 | } 82 | 83 | /** 84 | * Returns the value of the 16bit register whose index is given. 85 | * @param index CPU's internal register index. 86 | * @return the value of the 16bit register whose index is given. 87 | */ 88 | public short getReg16(byte index) { 89 | switch (index) { 90 | case 0: 91 | return m_state.getAX(); 92 | case 1: 93 | return m_state.getCX(); 94 | case 2: 95 | return m_state.getDX(); 96 | case 3: 97 | return m_state.getBX(); 98 | case 4: 99 | return m_state.getSP(); 100 | case 5: 101 | return m_state.getBP(); 102 | case 6: 103 | return m_state.getSI(); 104 | case 7: 105 | return m_state.getDI(); 106 | default: 107 | throw new RuntimeException(); 108 | } 109 | } 110 | 111 | /** 112 | * Sets the value of the 16bit register whose index is given. 113 | * @param index CPU's internal register index. 114 | * @param value New value for above register. 115 | */ 116 | public void setReg16(byte index, short value) { 117 | switch (index) { 118 | case 0: 119 | m_state.setAX(value); 120 | break; 121 | case 1: 122 | m_state.setCX(value); 123 | break; 124 | case 2: 125 | m_state.setDX(value); 126 | break; 127 | case 3: 128 | m_state.setBX(value); 129 | break; 130 | case 4: 131 | m_state.setSP(value); 132 | break; 133 | case 5: 134 | m_state.setBP(value); 135 | break; 136 | case 6: 137 | m_state.setSI(value); 138 | break; 139 | case 7: 140 | m_state.setDI(value); 141 | break; 142 | default: 143 | throw new RuntimeException(); 144 | } 145 | } 146 | 147 | /** 148 | * Returns the value of the segment register whose index is given. 149 | * @param index CPU's internal register index. 150 | * @return the value of the segment register whose index is given. 151 | */ 152 | public short getSeg(byte index) { 153 | switch (index) { 154 | case 0: 155 | return m_state.getES(); 156 | case 1: 157 | return m_state.getCS(); 158 | case 2: 159 | return m_state.getSS(); 160 | case 3: 161 | return m_state.getDS(); 162 | case 4: 163 | return m_state.getES(); 164 | case 5: 165 | return m_state.getCS(); 166 | case 6: 167 | return m_state.getSS(); 168 | case 7: 169 | return m_state.getDS(); 170 | default: 171 | throw new RuntimeException(); 172 | } 173 | } 174 | 175 | /** 176 | * Sets the value of the segment register whose index is given. 177 | * @param index CPU's internal register index. 178 | * @param value New value for above register. 179 | */ 180 | public void setSeg(byte index, short value) { 181 | switch (index) { 182 | case 0: 183 | m_state.setES(value); 184 | break; 185 | case 1: 186 | m_state.setCS(value); 187 | break; 188 | case 2: 189 | m_state.setSS(value); 190 | break; 191 | case 3: 192 | m_state.setDS(value); 193 | break; 194 | case 4: 195 | m_state.setES(value); 196 | break; 197 | case 5: 198 | m_state.setCS(value); 199 | break; 200 | case 6: 201 | m_state.setSS(value); 202 | break; 203 | case 7: 204 | m_state.setDS(value); 205 | break; 206 | default: 207 | throw new RuntimeException(); 208 | } 209 | } 210 | 211 | /** Used to access the actual registers */ 212 | private final CpuState m_state; 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/UnimplementedOpcodeException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Thrown when attempting to execute an unimplemented opcode. 5 | * 6 | * @author DL 7 | */ 8 | public class UnimplementedOpcodeException extends CpuException { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/cpu/UnsupportedOpcodeException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.cpu; 2 | 3 | /** 4 | * Thrown when attempting to execute an unsupported opcode. 5 | * 6 | * @author DL 7 | */ 8 | public class UnsupportedOpcodeException extends CpuException { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/Canvas.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.utils.EventMulticaster; 4 | 5 | import java.awt.Color; 6 | import java.awt.Dimension; 7 | import java.awt.Graphics; 8 | import java.awt.event.MouseEvent; 9 | 10 | import javax.swing.JComponent; 11 | import javax.swing.event.MouseInputListener; 12 | 13 | 14 | /** 15 | * @author BS 16 | */ 17 | public class Canvas extends JComponent implements MouseInputListener { 18 | private static final long serialVersionUID = 1L; 19 | 20 | public static final int BOARD_SIZE = 256; 21 | public static final int DOT_SIZE = 3; 22 | public static final byte EMPTY = -1; 23 | 24 | private byte[][] data; 25 | 26 | private boolean[][] pointer; 27 | 28 | private EventMulticaster eventCaster; 29 | private MouseAddressRequest eventHandler; 30 | 31 | private int MouseX, MouseY; 32 | 33 | public Canvas() { 34 | eventCaster = new EventMulticaster(MouseAddressRequest.class); 35 | eventHandler = (MouseAddressRequest) eventCaster.getProxy(); 36 | this.addMouseMotionListener(this); 37 | this.addMouseListener(this); 38 | this.MouseX = 0; 39 | this.MouseY = 0; 40 | clear(); 41 | } 42 | 43 | @Override 44 | public Dimension getMinimumSize() { 45 | return new Dimension(BOARD_SIZE * DOT_SIZE, BOARD_SIZE * DOT_SIZE); 46 | } 47 | 48 | @Override 49 | public Dimension getPreferredSize() { 50 | return getMinimumSize(); 51 | } 52 | 53 | public void paintPixel(int number, byte color) { 54 | paintPixel(number % BOARD_SIZE, number / BOARD_SIZE, color); 55 | } 56 | 57 | public void paintPixel(int x, int y, byte color) { 58 | data[x][y] = color; 59 | Graphics g = getGraphics(); 60 | if (g != null) { 61 | g.setColor(ColorHolder.getInstance().getColor(color,false)); 62 | g.fillRect(x * DOT_SIZE, y * DOT_SIZE, DOT_SIZE, DOT_SIZE); 63 | } 64 | } 65 | 66 | /** 67 | * Get the color of warrior id 68 | */ 69 | public Color getColorForWarrior(int id) { 70 | return ColorHolder.getInstance().getColor(id,false); 71 | } 72 | 73 | 74 | public void paintPointer(int x, int y, Color color) { 75 | pointer[x][y] = true; 76 | Graphics g = getGraphics(); 77 | if (g != null) { 78 | g.setColor(color); 79 | g.fillRect(x * DOT_SIZE, y * DOT_SIZE, DOT_SIZE, DOT_SIZE); 80 | } 81 | } 82 | 83 | public void paintPointer(int x, int y, byte color) { 84 | this.paintPointer(x, y, ColorHolder.getInstance().getColor(color, true)); 85 | } 86 | 87 | public void paintPointer(int number, byte color) { 88 | this.paintPointer(number % BOARD_SIZE, number / BOARD_SIZE, color); 89 | } 90 | 91 | /** 92 | * Clears the entire canvas 93 | */ 94 | public void clear() { 95 | data = new byte[BOARD_SIZE][BOARD_SIZE]; 96 | pointer = new boolean[BOARD_SIZE][BOARD_SIZE]; 97 | for (int i = 0; i < BOARD_SIZE; i++) 98 | for (int j = 0; j < BOARD_SIZE; j++) { 99 | data[i][j] = EMPTY; 100 | pointer[i][j] = false; 101 | } 102 | repaint(); 103 | } 104 | 105 | /** 106 | * When we have to - repaint the entire canvas 107 | */ 108 | @Override 109 | public void paint(Graphics g) { 110 | g.fillRect(0,0, BOARD_SIZE * DOT_SIZE, BOARD_SIZE * DOT_SIZE); 111 | 112 | for (int y = 0; y < BOARD_SIZE; y++) { 113 | for (int x = 0; x < BOARD_SIZE; x++) { 114 | int cellVal = data[x][y]; 115 | if (cellVal == EMPTY) { 116 | continue; 117 | } 118 | 119 | g.setColor(ColorHolder.getInstance().getColor(cellVal,false)); 120 | g.fillRect(x*DOT_SIZE, y*DOT_SIZE, DOT_SIZE, DOT_SIZE); 121 | } 122 | } 123 | } 124 | 125 | @Override 126 | public void mouseMoved(MouseEvent e) { 127 | 128 | Graphics g = this.getGraphics(); 129 | 130 | if (g != null) { 131 | // delete Mouse 132 | this.clearMousePointer(g); 133 | 134 | if (true) { 135 | MouseX = e.getX() / DOT_SIZE; 136 | MouseY = e.getY() / DOT_SIZE; 137 | 138 | // draw new Mouse 139 | g.setColor(Color.WHITE); 140 | 141 | g.fillRect(MouseX * DOT_SIZE, MouseY * DOT_SIZE, DOT_SIZE, 142 | DOT_SIZE); 143 | } 144 | } 145 | } 146 | 147 | private void clearMousePointer(Graphics g) { 148 | try { 149 | g.setColor(ColorHolder.getInstance() 150 | .getColor(data[MouseX][MouseY],false)); 151 | } catch (Exception ex) { 152 | // TODO the true variable of the color 153 | g.setColor(new Color(51, 51, 51)); 154 | } 155 | g.fillRect(MouseX * DOT_SIZE, MouseY * DOT_SIZE, DOT_SIZE, DOT_SIZE); 156 | } 157 | 158 | @Override 159 | public void mouseClicked(MouseEvent arg0) { 160 | eventHandler.addressAtMouseLocationRequested(this.MouseX + BOARD_SIZE* this.MouseY); 161 | } 162 | 163 | @Override 164 | public void mouseExited(MouseEvent arg0) { 165 | this.clearMousePointer(this.getGraphics()); 166 | } 167 | 168 | public void addListener(MouseAddressRequest l) { 169 | eventCaster.add(l); 170 | } 171 | 172 | public void deletePointers() { 173 | for (int i = 0; i < BOARD_SIZE; i++) 174 | for (int j = 0; j < BOARD_SIZE; j++) { 175 | if (pointer[i][j] == true && data[i][j] != EMPTY) { 176 | pointer[i][j] = false; 177 | paintPixel(i, j, data[i][j]); 178 | } 179 | } 180 | } 181 | 182 | @Override 183 | public void mouseEntered(MouseEvent arg0) { 184 | // TODO Auto-generated method stub 185 | 186 | } 187 | 188 | @Override 189 | public void mousePressed(MouseEvent arg0) { 190 | // TODO Auto-generated method stub 191 | 192 | } 193 | 194 | @Override 195 | public void mouseReleased(MouseEvent arg0) { 196 | // TODO Auto-generated method stub 197 | 198 | } 199 | 200 | @Override 201 | public void mouseDragged(MouseEvent arg0) { 202 | // TODO Auto-generated method stub 203 | 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/ColorHolder.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import java.awt.Color; 4 | 5 | /** 6 | * @author BS 7 | */ 8 | public class ColorHolder { 9 | private Color colors[]; 10 | private Color darkColors[]; 11 | public static final int MAX_COLORS = 360; 12 | private static ColorHolder ins = new ColorHolder(MAX_COLORS); 13 | 14 | private ColorHolder(int numPlayers) { 15 | // see http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ 16 | 17 | colors = new Color[numPlayers]; 18 | float golden_ratio_conjugate = 0.618033988749895f; 19 | float x = 0; 20 | for (int i = 0; i < MAX_COLORS; i++) { 21 | colors[i] = Color.getHSBColor(x % 1, 0.8f, 0.95f); 22 | x += golden_ratio_conjugate; 23 | } 24 | 25 | darkColors = new Color[colors.length]; 26 | for (int i = 0; i < colors.length; i++) { 27 | darkColors[i] = colors[i].darker(); 28 | } 29 | } 30 | 31 | public static ColorHolder getInstance() { 32 | return ins; 33 | } 34 | 35 | public Color getColor(int pos, boolean darker) { 36 | if (darker) { 37 | return darkColors[pos]; 38 | } else { 39 | return colors[pos]; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/ColumnGraph.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | 5 | import java.awt.Color; 6 | import java.awt.Dimension; 7 | import java.awt.Font; 8 | import java.awt.Graphics; 9 | import java.awt.Graphics2D; 10 | 11 | import javax.swing.JComponent; 12 | 13 | /** 14 | * @author BS 15 | */ 16 | public class ColumnGraph extends JComponent { 17 | private static final long serialVersionUID = 1L; 18 | 19 | private float[][] values; 20 | private String[] names; 21 | private float maxValue; 22 | private double reduceFactor; 23 | private long numTopTeams; 24 | private TeamColorHolder teamColorHolder; 25 | 26 | private static final int NAME_HEIGHT = 14; 27 | // We assume the teams' first 3 characters are the school name 28 | private static final int SCHOOL_PREFIX_LEN = 3; 29 | 30 | public ColumnGraph(String[] names, Options options) { 31 | super(); 32 | this.names = new String[names.length]; 33 | // the first element holds the sum of all the other values 34 | values = new float[names.length][3]; 35 | System.arraycopy(names, 0, this.names, 0, names.length); 36 | maxValue = 0; 37 | reduceFactor = 5; 38 | numTopTeams = Math.min(names.length / 2, 10); // If you are in the top half you count as top team 39 | // Give out colors by the team name first 3 characters 40 | // So teams from the same school have the same color 41 | teamColorHolder = new TeamColorHolder(names, SCHOOL_PREFIX_LEN, options); 42 | } 43 | 44 | @Override 45 | public Dimension getMinimumSize() { 46 | return new Dimension(500,500); 47 | } 48 | 49 | @Override 50 | public Dimension getPreferredSize() { 51 | return getMinimumSize(); 52 | } 53 | 54 | public void addToValue(int pos, int subIndex, float value) { 55 | values[pos][0]+= value; 56 | values[pos][subIndex+1]+= value; 57 | 58 | if (values[pos][0] > maxValue) { 59 | // reset graph factor by half to make more room 60 | maxValue = values[pos][0]; 61 | if (maxValue * reduceFactor > getSize().height-10) { 62 | reduceFactor *= 0.5; 63 | } 64 | } 65 | repaint(); 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see javax.swing.JComponent#paintComponent(java.awt.Graphics) 70 | */ 71 | protected void paintComponent(Graphics g) { 72 | Dimension d = getSize(); 73 | g.setColor(Color.BLACK); 74 | g.fillRect(0,0,d.width, d.height); 75 | d.setSize(d.width, d.height - NAME_HEIGHT); 76 | g.drawRect(0,0,d.width, d.height); 77 | final int numPlayers = names.length; 78 | int columnWidth = d.width / numPlayers; 79 | 80 | ColorHolder colorHolder= ColorHolder.getInstance(); 81 | for (int i = 0; i < numPlayers; i++) { 82 | paintColumn(g, i, columnWidth, d.height, colorHolder); 83 | g.setColor(getTeamColor(i, false)); 84 | g.drawString(names[i], i*columnWidth+5, d.height+NAME_HEIGHT-2); 85 | } 86 | } 87 | 88 | private int getTeamRating(int teamCol) { 89 | int rating = 1; 90 | for (int i = 0; i < values.length; i++) { 91 | // For every different team that has a higher score, this teams ranks one less 92 | // For a tie, both teams lose one rating score 93 | if (teamCol != i && values[teamCol][0] <= values[i][0]) { 94 | rating++; 95 | } 96 | } 97 | return rating; 98 | } 99 | 100 | private boolean isTopTeam(int teamCol) { 101 | return getTeamRating(teamCol) <= numTopTeams; 102 | } 103 | 104 | private Color getTeamColor(int teamIndex, boolean darker) { 105 | return teamColorHolder.getColor(names[teamIndex], darker); 106 | } 107 | 108 | // Copied from this stackover flow question - https://stackoverflow.com/questions/10083913/how-to-rotate-text-with-graphics2d-in-java 109 | public static void drawRotatedString(Graphics g, double x, double y, int angle, String text) { 110 | Graphics2D g2d = (Graphics2D)g; 111 | g2d.translate((float)x,(float)y); 112 | g2d.rotate(Math.toRadians(angle)); 113 | g2d.drawString(text,0,0); 114 | g2d.rotate(-Math.toRadians(angle)); 115 | g2d.translate(-(float)x,-(float)y); 116 | } 117 | 118 | public static Color invertColor(Color c) { 119 | int r = 255 - c.getRed(); 120 | int g = 255 - c.getGreen(); 121 | int b = 255 - c.getBlue(); 122 | return new Color(r, g, b, c.getAlpha()); 123 | } 124 | 125 | private void paintColumn(Graphics g, int col, int width, int startHeight, ColorHolder colorHolder) { 126 | g.setColor(getTeamColor(col, false)); 127 | int height1 = (int) (reduceFactor*values[col][1]); 128 | g.fill3DRect(col*width, startHeight - height1, width, height1, true); 129 | g.setColor(getTeamColor(col, true)); 130 | int height2 = (int) (reduceFactor*values[col][2]); 131 | int boxTopY = startHeight - height1 - height2; 132 | g.fill3DRect(col*width, boxTopY, width, height2, false); 133 | g.drawString("" + (int)(values[col][0] * 10)/10.0f, col*width, boxTopY - 5); 134 | 135 | 136 | if (isTopTeam(col)) { 137 | Font origFontBackup = g.getFont(); 138 | // For top teams, draw their initials on top of the rectangle 139 | // This should be their school initials - for example, 140 | // "OST" for Ostrovski or "GBA" for GreenBlitz Academy 141 | String teamInitials = names[col].substring(0, Math.min(SCHOOL_PREFIX_LEN, names[col].length())).toUpperCase(); 142 | 143 | // About font size - the number specified is the font's "em height" 144 | // See this stackoverflow question - https://graphicdesign.stackexchange.com/questions/4035/what-does-the-size-of-the-font-translate-to-exactly 145 | // We treat as approximately the max height of a character 146 | // wild guess - The width of the common character will be around the same 147 | int teamInitialsFontSize = Math.min(width/2, 100); 148 | g.setFont(new Font(Font.MONOSPACED, Font.BOLD, teamInitialsFontSize)); 149 | g.drawString(teamInitials, col*width, boxTopY - 25); 150 | 151 | 152 | 153 | Color bakColor = g.getColor(); 154 | Color gold = new Color(255, 215, 0); 155 | g.setColor(gold); 156 | // Draw the rating (e.g "1") on top of the box, in gold 157 | int ratingFontSize = (int)Math.min(width*1.3, 180); 158 | g.setFont(new Font(Font.MONOSPACED, Font.BOLD, ratingFontSize)); 159 | g.drawString(String.valueOf(getTeamRating(col)), (int)((col + 0.1)*(width)), boxTopY - teamInitialsFontSize - 35); 160 | g.setColor(bakColor); 161 | 162 | 163 | 164 | // Here the font size should match: 165 | // The font height is the width because it's horizontal 166 | int rotatedNameFontSize = (int)Math.min(width * 0.6, 140); 167 | g.setFont(new Font(Font.MONOSPACED, Font.BOLD, rotatedNameFontSize)); 168 | int charwidth = g.getFontMetrics().charWidth('A'); // monospaced so all char widths are the same 169 | int lettersCanFitInRect = (int)((height1 + height2) / charwidth); 170 | String nameCanFit = names[col].substring(0, Math.min(lettersCanFitInRect, names[col].length())); 171 | 172 | // Invert the color so we can see best 173 | Color curColor = g.getColor(); 174 | g.setColor(invertColor(curColor)); 175 | drawRotatedString(g, col*width + g.getFontMetrics().getAscent()/2, boxTopY + 5, 90, nameCanFit); 176 | g.setColor(curColor); 177 | 178 | 179 | g.setFont(origFontBackup); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/CompetitionWindow.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | import il.co.codeguru.corewars8086.war.Competition; 5 | import il.co.codeguru.corewars8086.war.CompetitionEventListener; 6 | import il.co.codeguru.corewars8086.war.ScoreEventListener; 7 | import il.co.codeguru.corewars8086.war.WarriorRepository; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | import java.awt.event.WindowEvent; 14 | import java.awt.event.WindowListener; 15 | import java.io.IOException; 16 | 17 | /** 18 | * @author BS 19 | */ 20 | public class CompetitionWindow extends JFrame 21 | implements ScoreEventListener, ActionListener, CompetitionEventListener { 22 | private static final long serialVersionUID = 1L; 23 | 24 | private final Competition competition; 25 | private final ColumnGraph columnGraph; 26 | 27 | // widgets 28 | private final JButton runWarButton; 29 | private final JLabel warCounterDisplay; 30 | private final JCheckBox showBattleCheckBox; 31 | private final JTextField battlesPerGroupField; 32 | private final JTextField warriorsPerGroupField; 33 | private WarFrame battleFrame; 34 | 35 | private int warCounter; 36 | private int totalWars; 37 | private Thread warThread; 38 | private boolean competitionRunning; 39 | 40 | private static final String SEED_PREFIX = "SEED!@#="; 41 | private final JTextField seed; 42 | 43 | private final JCheckBox startPausedCheckBox; 44 | 45 | private final Options options; 46 | 47 | public CompetitionWindow(Options options) throws IOException { 48 | super("CodeGuru Extreme - Competition Viewer"); 49 | this.options = options; 50 | getContentPane().setLayout(new BorderLayout()); 51 | setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 52 | competition = new Competition(options); 53 | competition.addCompetitionEventListener(this); 54 | WarriorRepository warriorRepository = competition.getWarriorRepository(); 55 | warriorRepository.addScoreEventListener(this); 56 | columnGraph = new ColumnGraph(warriorRepository.getGroupNames(), options); 57 | getContentPane().add(columnGraph, BorderLayout.CENTER); 58 | // ------------- 59 | JPanel controlArea = new JPanel(); 60 | controlArea.setLayout(new BoxLayout(controlArea, BoxLayout.Y_AXIS)); 61 | // -------------- Button Panel 62 | JPanel buttonPanel = new JPanel(); 63 | runWarButton = new JButton("Start!"); 64 | runWarButton.addActionListener(this); 65 | buttonPanel.add(runWarButton); 66 | warCounterDisplay = new JLabel(""); 67 | buttonPanel.add(warCounterDisplay); 68 | buttonPanel.add(Box.createHorizontalStrut(30)); 69 | showBattleCheckBox = new JCheckBox("Show war on start"); 70 | buttonPanel.add(showBattleCheckBox); 71 | 72 | startPausedCheckBox = new JCheckBox("Start Paused"); 73 | startPausedCheckBox.setEnabled(!options.parallel); // TODO enable functionality in 5.0.1 74 | startPausedCheckBox.addActionListener(event -> { 75 | if (startPausedCheckBox.isSelected()) 76 | showBattleCheckBox.setSelected(true); 77 | }); 78 | buttonPanel.add(startPausedCheckBox); 79 | 80 | controlArea.add(buttonPanel); 81 | // ------------- 82 | controlArea.add(new JSeparator(JSeparator.HORIZONTAL)); 83 | // ------------ Control panel 84 | JPanel controlPanel = new JPanel(); 85 | controlPanel.setLayout(new FlowLayout()); 86 | controlPanel.add(new JLabel("Survivor groups per war:")); 87 | 88 | // If total number of teams is less then four, make it the defauld number 89 | int numberOfGroups = Math.min(4, 90 | competition.getWarriorRepository().getNumberOfGroups()); 91 | 92 | warriorsPerGroupField = new JTextField(String.format("%d", numberOfGroups), 3); 93 | controlPanel.add(warriorsPerGroupField); 94 | controlPanel.add(new JLabel("Wars per groups combination:")); 95 | battlesPerGroupField = new JTextField("100", 4); 96 | controlPanel.add(battlesPerGroupField); 97 | seed = new JTextField(15); 98 | seed.setText("guru"); 99 | controlPanel.add(new JLabel("seed:")); 100 | controlPanel.add(seed); 101 | 102 | controlArea.add(controlPanel); 103 | 104 | // ------------ 105 | getContentPane().add(controlArea, BorderLayout.SOUTH); 106 | 107 | addWindowListener(new WindowListener() { 108 | public void windowOpened(WindowEvent e) { 109 | } 110 | 111 | public void windowClosing(WindowEvent e) { 112 | if (warThread != null) { 113 | competition.setAbort(true); 114 | } 115 | } 116 | 117 | public void windowClosed(WindowEvent e) { 118 | } 119 | 120 | public void windowIconified(WindowEvent e) { 121 | } 122 | 123 | public void windowDeiconified(WindowEvent e) { 124 | } 125 | 126 | public void windowActivated(WindowEvent e) { 127 | } 128 | 129 | public void windowDeactivated(WindowEvent e) { 130 | } 131 | }); 132 | } 133 | 134 | /** 135 | * Starts a new war. 136 | * 137 | * @return whether or not a new war was started. 138 | */ 139 | public boolean runWar() { 140 | try { 141 | long seedValue; 142 | if (seed.getText().startsWith(SEED_PREFIX)) { 143 | seedValue = Long.parseLong(seed.getText().substring(SEED_PREFIX.length())); 144 | } else { 145 | seedValue = seed.getText().hashCode(); 146 | } 147 | competition.setSeed(seedValue); 148 | final int battlesPerGroup = Integer.parseInt( 149 | battlesPerGroupField.getText().trim()); 150 | final int warriorsPerGroup = Integer.parseInt( 151 | warriorsPerGroupField.getText().trim()); 152 | if (competition.getWarriorRepository().getNumberOfGroups() < warriorsPerGroup) { 153 | JOptionPane.showMessageDialog(this, 154 | "Not enough survivors (got " + 155 | competition.getWarriorRepository().getNumberOfGroups() + 156 | " but " + warriorsPerGroup + " are needed)"); 157 | return false; 158 | } 159 | warThread = new Thread("CompetitionThread") { 160 | @Override 161 | public void run() { 162 | try { 163 | if (options.parallel) { 164 | competition.runCompetitionInParallel(battlesPerGroup, warriorsPerGroup, options.threads); 165 | } else { 166 | competition.runCompetition(battlesPerGroup, warriorsPerGroup, startPausedCheckBox.isSelected()); 167 | } 168 | } catch (Exception e) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | }; 173 | if (!competitionRunning) { 174 | warThread.start(); 175 | return true; 176 | } 177 | } catch (NumberFormatException e2) { 178 | JOptionPane.showMessageDialog(this, "Error in configuration"); 179 | } 180 | return false; 181 | } 182 | 183 | public void scoreChanged(String name, float addedValue, int groupIndex, int subIndex) { 184 | SwingUtilities.invokeLater(() -> columnGraph.addToValue(groupIndex, subIndex, addedValue)); 185 | } 186 | 187 | public void actionPerformed(ActionEvent e) { 188 | if (e.getSource() == runWarButton) { 189 | showBattleFrameIfNeeded(); 190 | switch (runWarButton.getText().trim()) { 191 | case "Start!": 192 | if (runWar()) { 193 | competitionRunning = true; 194 | 195 | if (options.parallel) { 196 | showBattleCheckBox.setEnabled(false); // TODO enable functionality in 5.0.1 197 | } 198 | } 199 | break; 200 | case ("Stop!"): 201 | competition.setAbort(true); 202 | competitionRunning = false; 203 | break; 204 | default: 205 | break; 206 | } 207 | } 208 | } 209 | 210 | 211 | @Override 212 | public void onWarStart(long seed) { 213 | SwingUtilities.invokeLater(() -> { 214 | this.seed.setText(SEED_PREFIX + seed); 215 | showBattleFrameIfNeeded(); 216 | }); 217 | } 218 | 219 | private void showBattleFrameIfNeeded() { 220 | if (showBattleCheckBox.isSelected() && battleFrame == null) { 221 | showBattleRoom(); 222 | showBattleCheckBox.setSelected(false); 223 | 224 | if (options.parallel) { 225 | showBattleCheckBox.setEnabled(false); 226 | } 227 | } 228 | } 229 | 230 | private void showBattleRoom() { 231 | competition.setSpeed(5); 232 | battleFrame = new WarFrame(competition); 233 | battleFrame.addWindowListener(new WindowListener() { 234 | public void windowOpened(WindowEvent e) { 235 | } 236 | 237 | public void windowClosing(WindowEvent e) { 238 | } 239 | 240 | public void windowClosed(WindowEvent e) { 241 | //System.out.println("BattleFrame=null"); 242 | battleFrame = null; 243 | competition.setSpeed(Competition.MAXIMUM_SPEED); 244 | } 245 | 246 | public void windowIconified(WindowEvent e) { 247 | } 248 | 249 | public void windowDeiconified(WindowEvent e) { 250 | } 251 | 252 | public void windowActivated(WindowEvent e) { 253 | } 254 | 255 | public void windowDeactivated(WindowEvent e) { 256 | } 257 | }); 258 | 259 | competition.addMemoryEventLister(battleFrame); 260 | competition.addCompetitionEventListener(battleFrame); 261 | Rectangle battleFrameRect = new Rectangle(0, getY(), 750, 700); 262 | Rectangle screen = getGraphicsConfiguration().getBounds(); //for multiple monitors 263 | 264 | if (getX() + getWidth() <= screen.getX() + screen.getWidth() 265 | - battleFrameRect.width) { 266 | battleFrameRect.x = getX() + getWidth(); 267 | } else if (screen.getX() + screen.getWidth() - battleFrameRect.width 268 | - getWidth() >= screen.getX()) { 269 | setLocation((int) (screen.getX() + screen.getWidth() - battleFrameRect.width 270 | - getWidth()), getY()); 271 | battleFrameRect.x = getX() + getWidth(); 272 | } else { 273 | setLocation((int) screen.getX(), getY()); 274 | battleFrameRect.x = getWidth(); 275 | } 276 | 277 | battleFrame.setBounds(battleFrameRect); 278 | battleFrame.setVisible(true); 279 | } 280 | 281 | public void onWarEnd(int reason, String winners) { 282 | warCounter++; 283 | seed.setText(SEED_PREFIX + competition.getSeed()); 284 | SwingUtilities.invokeLater( 285 | () -> warCounterDisplay.setText("Wars so far:" + warCounter + " (out of " + totalWars + ")") 286 | ); 287 | } 288 | 289 | public void onRound(int round) { 290 | } 291 | 292 | public void onWarriorBirth(String warriorName) { 293 | } 294 | 295 | public void onWarriorDeath(String warriorName, String reason) { 296 | } 297 | 298 | public void onCompetitionStart() { 299 | warCounter = 0; 300 | totalWars = competition.getTotalNumberOfWars(); 301 | competition.setAbort(false); 302 | runWarButton.setText("Stop!"); 303 | } 304 | 305 | public void onCompetitionEnd() { 306 | SwingUtilities.invokeLater(() -> { 307 | warCounterDisplay.setText(String.format("The competition is over. %d wars were run.", warCounter)); 308 | runWarButton.setText("Start!"); 309 | showBattleCheckBox.setEnabled(true); 310 | }); 311 | warThread = null; 312 | competitionRunning = false; 313 | } 314 | 315 | @Override 316 | public void onEndRound() { 317 | } 318 | 319 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/CpuFrame.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 4 | import il.co.codeguru.corewars8086.utils.Disassembler; 5 | import il.co.codeguru.corewars8086.war.Competition; 6 | import il.co.codeguru.corewars8086.war.CompetitionEventListener; 7 | import il.co.codeguru.corewars8086.war.War; 8 | 9 | import java.awt.Font; 10 | import java.awt.GridLayout; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | 14 | import javax.swing.JButton; 15 | import javax.swing.JComboBox; 16 | import javax.swing.JFrame; 17 | import javax.swing.JOptionPane; 18 | import javax.swing.JPanel; 19 | import javax.swing.JTextArea; 20 | 21 | public class CpuFrame extends JFrame implements CompetitionEventListener { 22 | 23 | private War currentWar; 24 | 25 | private Competition competition; 26 | 27 | private JPanel menuPanel; 28 | 29 | private JComboBox dropMenu; 30 | 31 | private JPanel regPanel,flagPanel; 32 | 33 | private RegisterField regAX,regBX,regCX,regDX, 34 | regSI,regDI,regBP,regSP, 35 | regIP,regCS,regDS,regSS, 36 | regES,regE; 37 | 38 | private FlagFields flagOF,flagDF,flagIF,flagTF, 39 | flagSF,flagZF,flagAF,flagPF, 40 | flagCF; 41 | 42 | private final JButton btnRefresh; 43 | private final JButton btnSave; 44 | 45 | private JTextArea instructionArea; 46 | 47 | 48 | public CpuFrame(Competition c){ 49 | super("CPU state viewer - CodeGuru"); 50 | this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); 51 | this.setSize(550, 400); 52 | this.competition = c; 53 | this.currentWar = c.getCurrentWar(); 54 | 55 | GridLayout l = new GridLayout(0,2); 56 | l.setVgap(5); 57 | l.setHgap(5); 58 | 59 | //registerFileds 60 | regPanel = new JPanel(l); 61 | 62 | this.setAlwaysOnTop(true); 63 | 64 | regAX = new RegisterField("AX"); regPanel.add(regAX); 65 | regBX = new RegisterField("BX"); regPanel.add(regBX); 66 | regCX = new RegisterField("CX"); regPanel.add(regCX); 67 | regDX = new RegisterField("DX"); regPanel.add(regDX); 68 | regSI = new RegisterField("SI"); regPanel.add(regSI); 69 | regDI = new RegisterField("DI"); regPanel.add(regDI); 70 | regBP = new RegisterField("BP"); regPanel.add(regBP); 71 | regSP = new RegisterField("SP"); regPanel.add(regSP); 72 | regIP = new RegisterField("IP"); regPanel.add(regIP); 73 | regCS = new RegisterField("CS"); regPanel.add(regCS); 74 | regDS = new RegisterField("DS"); regPanel.add(regDS); 75 | regSS = new RegisterField("SS"); regPanel.add(regSS); 76 | regES = new RegisterField("ES"); regPanel.add(regES); 77 | regE = new RegisterField("Energy"); regPanel.add(regE); 78 | 79 | //Flags 80 | flagPanel = new JPanel(l); 81 | 82 | flagOF = new FlagFields("OF"); flagPanel.add(flagOF); 83 | flagDF = new FlagFields("DF"); flagPanel.add(flagDF); 84 | flagIF = new FlagFields("IF"); flagPanel.add(flagIF); 85 | flagTF = new FlagFields("TF"); flagPanel.add(flagTF); 86 | flagSF = new FlagFields("SF"); flagPanel.add(flagSF); 87 | flagZF = new FlagFields("ZF"); flagPanel.add(flagZF); 88 | flagAF = new FlagFields("AF"); flagPanel.add(flagAF); 89 | flagPF = new FlagFields("PF"); flagPanel.add(flagPF); 90 | flagCF = new FlagFields("CF"); flagPanel.add(flagCF); 91 | 92 | //menu panel 93 | 94 | menuPanel = new JPanel(); 95 | 96 | dropMenu = new JComboBox<>(); 97 | for( int i = 0 ; i < this.currentWar.getNumWarriors() ; i++ ) 98 | dropMenu.addItem(this.currentWar.getWarrior(i).getName()); 99 | 100 | dropMenu.addActionListener(e -> updateFields()); 101 | 102 | btnRefresh = new JButton("Refresh"); 103 | btnRefresh.addActionListener(e -> updateFields()); 104 | 105 | btnSave = new JButton("Save"); 106 | btnSave.addActionListener(e -> saveFields()); 107 | 108 | instructionArea = new JTextArea(); 109 | instructionArea.setFont(new Font("Monospaced",Font.PLAIN,15)); 110 | instructionArea.setSize(50, 100); 111 | instructionArea.setLineWrap(true); 112 | instructionArea.setWrapStyleWord(true); 113 | 114 | this.updateFields(); 115 | 116 | menuPanel.add(dropMenu); 117 | menuPanel.add(btnRefresh); 118 | menuPanel.add(btnSave); 119 | 120 | JPanel cpuPanel = new JPanel(new GridLayout(1, 3)); 121 | cpuPanel.add(menuPanel); 122 | cpuPanel.add(flagPanel); 123 | cpuPanel.add(regPanel); 124 | 125 | this.add(cpuPanel); 126 | this.add(instructionArea); 127 | 128 | this.setLayout(new GridLayout(2,1,10,10)); 129 | this.setResizable(false); 130 | this.setVisible(true); 131 | } 132 | 133 | public void updateFields(){ 134 | regAX.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getAX()); 135 | regBX.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getBX()); 136 | regCX.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getCX()); 137 | regDX.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getDX()); 138 | regSI.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getSI()); 139 | regDI.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getDI()); 140 | regBP.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getBP()); 141 | regSP.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getSP()); 142 | regIP.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getIP()); 143 | regCS.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getCS()); 144 | regDS.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getDS()); 145 | regSS.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getSS()); 146 | regES.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getES()); 147 | regE.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getEnergy()); 148 | 149 | flagOF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getOverflowFlag()); 150 | flagDF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getDirectionFlag() ); 151 | flagIF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getInterruptFlag() ); 152 | flagTF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getTrapFlag() ); 153 | flagSF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getSignFlag() ); 154 | flagZF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getZeroFlag() ); 155 | flagAF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getAuxFlag() ); 156 | flagPF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getParityFlag() ); 157 | flagCF.setValue( currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getCarryFlag() ); 158 | 159 | byte[] bytes = new byte[30]; 160 | 161 | for (short i = 0; i < 30; i++) { 162 | short ip = currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getIP(); 163 | short cs = currentWar.getWarrior(dropMenu.getSelectedIndex()).getCpuState().getCS(); 164 | short vs = currentWar.getMemory().readByte(new RealModeAddress(cs, (short) (ip + i))); 165 | bytes[i] = (byte) vs; 166 | } 167 | 168 | try { 169 | instructionArea.setText(Disassembler.disassembler(bytes)); 170 | } catch (Exception e) { 171 | instructionArea.setText(e.getMessage()); 172 | e.printStackTrace(); 173 | } 174 | } 175 | 176 | public void saveFields(){ 177 | try { 178 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setAX(regAX.getValue()); 179 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setBX(regBX.getValue()); 180 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setCX(regCX.getValue()); 181 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setDX(regDX.getValue()); 182 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setSI(regSI.getValue()); 183 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setDI(regDI.getValue()); 184 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setBP(regBP.getValue()); 185 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setSP(regSP.getValue()); 186 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setIP(regIP.getValue()); 187 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setCS(regCS.getValue()); 188 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setDS(regDS.getValue()); 189 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setSS(regSS.getValue()); 190 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setES(regES.getValue()); 191 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setEnergy(regE.getValue()); 192 | } catch (Exception e) { 193 | JOptionPane.showMessageDialog(this, "You are trying to save invalid value!"); 194 | } 195 | 196 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setOverflowFlag(this.flagOF.getValue()); 197 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setDirectionFlag(this.flagDF.getValue()); 198 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setInterruptFlag(this.flagIF.getValue()); 199 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setTrapFlag(this.flagTF.getValue()); 200 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setSignFlag(this.flagSF.getValue()); 201 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setZeroFlag(this.flagZF.getValue()); 202 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setAuxFlag(this.flagAF.getValue()); 203 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setParityFlag(this.flagPF.getValue()); 204 | this.currentWar.getWarrior(this.dropMenu.getSelectedIndex()).getCpuState().setCarryFlag(this.flagCF.getValue()); 205 | } 206 | 207 | @Override 208 | public void onWarStart(long seed) { 209 | // TODO Auto-generated method stub 210 | 211 | } 212 | 213 | @Override 214 | public void onWarEnd(int reason, String winners) { 215 | // TODO Auto-generated method stub 216 | this.dispose(); 217 | } 218 | 219 | @Override 220 | public void onRound(int round) { 221 | // TODO Auto-generated method stub 222 | 223 | } 224 | 225 | @Override 226 | public void onWarriorBirth(String warriorName) { 227 | // TODO Auto-generated method stub 228 | 229 | } 230 | 231 | @Override 232 | public void onWarriorDeath(String warriorName, String reason) { 233 | // TODO Auto-generated method stub 234 | 235 | } 236 | 237 | @Override 238 | public void onCompetitionStart() { 239 | // TODO Auto-generated method stub 240 | 241 | } 242 | 243 | @Override 244 | public void onCompetitionEnd() { 245 | // TODO Auto-generated method stub 246 | 247 | } 248 | 249 | @Override 250 | public void onEndRound() { 251 | // TODO Auto-generated method stub 252 | this.updateFields(); 253 | } 254 | 255 | @Override 256 | public void dispose() { 257 | 258 | //bug fix - event casted while window is being disposed FIXME find a better solution 259 | this.competition.getCurrentWar().pause(); 260 | 261 | try{ 262 | Thread.sleep(300); 263 | } catch (Exception e) { 264 | 265 | } 266 | this.competition.removeCompetitionEventListener(this); 267 | this.competition.getCurrentWar().resume(); 268 | 269 | super.dispose(); 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/FlagFields.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.GridLayout; 5 | 6 | import javax.swing.JCheckBox; 7 | import javax.swing.JLabel; 8 | import javax.swing.JPanel; 9 | 10 | public class FlagFields extends JPanel { 11 | 12 | private JCheckBox checkBox; 13 | 14 | public FlagFields(String name) { 15 | 16 | super.setLayout(new GridLayout(1,2)); 17 | super.setSize(20,20); 18 | super.add(new JLabel(name + ":"),BorderLayout.LINE_START); 19 | 20 | checkBox = new JCheckBox(); 21 | 22 | super.add(checkBox,BorderLayout.LINE_START); 23 | } 24 | 25 | public void setValue(boolean value){ 26 | checkBox.setSelected(value); 27 | } 28 | 29 | public boolean getValue(){ 30 | return checkBox.isSelected(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/MemoryFrame.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.Font; 5 | import java.awt.GridLayout; 6 | import java.util.ArrayList; 7 | 8 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 9 | import il.co.codeguru.corewars8086.utils.Disassembler; 10 | import il.co.codeguru.corewars8086.war.Competition; 11 | import il.co.codeguru.corewars8086.war.CompetitionEventListener; 12 | 13 | import javax.swing.JFrame; 14 | import javax.swing.JLabel; 15 | import javax.swing.JPanel; 16 | import javax.swing.JTextArea; 17 | import javax.swing.JTextField; 18 | 19 | public class MemoryFrame extends JFrame implements CompetitionEventListener { 20 | 21 | private static final long serialVersionUID = 1L; 22 | ArrayList cells = new ArrayList(); 23 | ArrayList labels = new ArrayList(); 24 | 25 | Competition comp; 26 | private JTextArea instructionArea; 27 | private int last; 28 | 29 | public MemoryFrame(Competition c,int address){ 30 | super("Memory viewer - CodeGuru"); 31 | this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); 32 | comp = c; 33 | 34 | setAlwaysOnTop(true); 35 | setSize(800,800); 36 | setVisible(true); 37 | 38 | JPanel l = new JPanel(new GridLayout(0,9)); 39 | for (int i = 0; i < 32; i++) { 40 | JLabel jl = new JLabel(); 41 | jl.setMinimumSize(new Dimension(40, 20)); 42 | labels.add(jl); 43 | l.add(jl); 44 | for (int j = 0; j < 8; j++) { 45 | JTextField t = new JTextField(2); 46 | cells.add(t); 47 | l.add(t); 48 | } 49 | } 50 | 51 | add(l); 52 | 53 | 54 | instructionArea = new JTextArea(); 55 | instructionArea.setFont(new Font("Monospaced",Font.PLAIN,15)); 56 | instructionArea.setSize(50, 100); 57 | instructionArea.setLineWrap(true); 58 | instructionArea.setWrapStyleWord(true); 59 | 60 | JPanel top = new JPanel(new GridLayout(0,2)); 61 | top.add(l); 62 | top.add(instructionArea); 63 | 64 | add(top); 65 | 66 | refresh(address); 67 | 68 | } 69 | 70 | 71 | public void refresh(int address) { 72 | last = address; 73 | int i = 0; 74 | for (JLabel la : labels) { 75 | la.setText(String.format("%08X", address+i++*8).toUpperCase() + ":"); 76 | } 77 | 78 | i = 0; 79 | for (JTextField tf : cells) { 80 | byte b = comp.getCurrentWar().getMemory().readByte(new RealModeAddress(address + i++)); 81 | tf.setText("" + String.format("%02X", b & 0xFF)); 82 | } 83 | 84 | 85 | byte[] bytes = new byte[30]; 86 | 87 | for (short k = 0; k < 30; k++) { 88 | short vs = comp.getCurrentWar().getMemory().readByte(new RealModeAddress(address +k)); 89 | bytes[k] = (byte) vs; 90 | } 91 | 92 | try { 93 | instructionArea.setText(Disassembler.disassembler(bytes)); 94 | } catch (Exception e) { 95 | instructionArea.setText(e.getMessage()); 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | @Override 101 | public void onEndRound() { 102 | refresh(last); 103 | } 104 | 105 | 106 | @Override 107 | public void onWarStart(long seed) { 108 | // TODO Auto-generated method stub 109 | 110 | } 111 | 112 | 113 | @Override 114 | public void onWarEnd(int reason, String winners) { 115 | // TODO Auto-generated method stub 116 | 117 | } 118 | 119 | 120 | @Override 121 | public void onRound(int round) { 122 | // TODO Auto-generated method stub 123 | 124 | } 125 | 126 | 127 | @Override 128 | public void onWarriorBirth(String warriorName) { 129 | // TODO Auto-generated method stub 130 | 131 | } 132 | 133 | 134 | @Override 135 | public void onWarriorDeath(String warriorName, String reason) { 136 | // TODO Auto-generated method stub 137 | 138 | } 139 | 140 | 141 | @Override 142 | public void onCompetitionStart() { 143 | // TODO Auto-generated method stub 144 | 145 | } 146 | 147 | 148 | @Override 149 | public void onCompetitionEnd() { 150 | // TODO Auto-generated method stub 151 | 152 | } 153 | 154 | 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/MouseAddressRequest.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import java.util.EventListener; 4 | 5 | public interface MouseAddressRequest extends EventListener { 6 | 7 | public void addressAtMouseLocationRequested(int address); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/RegisterField.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.GridLayout; 5 | 6 | import javax.swing.JLabel; 7 | import javax.swing.JPanel; 8 | import javax.swing.JTextField; 9 | 10 | public class RegisterField extends JPanel { 11 | 12 | private JTextField textField; 13 | 14 | public RegisterField(String name) { 15 | 16 | super.setLayout(new GridLayout(1, 2)); 17 | super.setSize(50, 20); 18 | super.add(new JLabel(name + ":"), BorderLayout.LINE_START); 19 | 20 | textField = new JTextField(2); 21 | 22 | super.add(textField, BorderLayout.LINE_START); 23 | } 24 | 25 | public void setValue(short value) { 26 | textField.setText(String.format("%04X", value).toUpperCase()); 27 | } 28 | 29 | public short getValue() throws Exception { 30 | return (short) Integer.parseInt(textField.getText(), 16); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/TeamColorHolder.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | 5 | import java.awt.Color; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.util.Hashtable; 10 | import java.util.List; 11 | 12 | /** 13 | * @author BS 14 | * @author RM 15 | */ 16 | public class TeamColorHolder { 17 | private final Hashtable colorHolder; 18 | private final int teamInitialsLength; 19 | 20 | private static final int COLUMNS_IN_COLORS_FILE = 2; 21 | 22 | private String getTeamInitials(String team) { 23 | return team.substring(0, Math.min(this.teamInitialsLength, team.length())).toUpperCase(); 24 | } 25 | 26 | public TeamColorHolder(String[] teamNames, int teamInitialsLength, Options options) { 27 | colorHolder = new Hashtable<>(); 28 | this.teamInitialsLength = teamInitialsLength; 29 | 30 | fillCenterColors(options.colorsFile); 31 | fillTeamColors(teamNames); 32 | } 33 | 34 | private void fillTeamColors(String[] teamNames) { 35 | // see http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ 36 | 37 | final float SATURATION = 0.8f; 38 | final float BRIGHTNESS = 0.95f; 39 | final float GOLDEN_RATIO_CONJUGATE = 0.618033988749895f; 40 | 41 | float x = 0; 42 | for (String teamName : teamNames) { 43 | String teamInitials = getTeamInitials(teamName); 44 | if (!colorHolder.contains(teamInitials)) { 45 | colorHolder.put(teamInitials, Color.getHSBColor(x % 1, SATURATION, BRIGHTNESS)); 46 | x += GOLDEN_RATIO_CONJUGATE; 47 | } 48 | } 49 | } 50 | 51 | private void fillCenterColors(String filename) { 52 | File colorsFile = new File(filename); 53 | if (!colorsFile.isFile()) return; 54 | 55 | try { 56 | List lines = Files.readAllLines(colorsFile.toPath()); 57 | 58 | for (String line : lines) { 59 | String[] split = line.split(",\\s*", COLUMNS_IN_COLORS_FILE); 60 | 61 | if (split.length < COLUMNS_IN_COLORS_FILE) continue; 62 | 63 | String center = split[0]; 64 | String rawColor = split[1]; 65 | 66 | if (center.length() != teamInitialsLength) continue; 67 | 68 | colorHolder.put(center.toUpperCase(), decodeHexColor(rawColor)); 69 | } 70 | } catch (IOException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | 75 | /** 76 | * Decodes a hex string of the format {@code #RRGGBB} (e.g., {@code "#00FF00"}) to a {@link java.awt.Color} 77 | */ 78 | private Color decodeHexColor(String rawColor) { 79 | if (!rawColor.matches("#[0-9a-fA-F]{6}")) { 80 | throw new NumberFormatException(String.format("%s is not a valid color string", rawColor)); 81 | } 82 | 83 | String hexColor = rawColor.replace("#", "0x"); 84 | return Color.decode(hexColor); 85 | } 86 | 87 | public Color getColor(String team, boolean darker) { 88 | String teamInitials = getTeamInitials(team); 89 | Color color = colorHolder.get(teamInitials); 90 | 91 | return darker ? color.darker() : color; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/gui/WarFrame.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.gui; 2 | 3 | import il.co.codeguru.corewars8086.memory.MemoryEventListener; 4 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 5 | import il.co.codeguru.corewars8086.utils.Unsigned; 6 | import il.co.codeguru.corewars8086.war.*; 7 | 8 | import java.awt.*; 9 | import java.util.Enumeration; 10 | 11 | import javax.swing.*; 12 | 13 | 14 | /** 15 | * The main GUI class for core-wars. 16 | * The frame includes: 17 | *

24 | * 25 | * @author BS 26 | */ 27 | public class WarFrame extends JFrame 28 | implements MemoryEventListener, CompetitionEventListener, MouseAddressRequest{ 29 | private static final long serialVersionUID = 1L; 30 | 31 | /** the canvas which show the core war memory area */ 32 | private Canvas warCanvas; 33 | 34 | /** the message area show misc. information about the current fight */ 35 | private JTextArea messagesArea; 36 | 37 | /** list of warrior names */ 38 | private JList nameList; 39 | 40 | /** Model for the name list */ 41 | private DefaultListModel nameListModel; 42 | 43 | /** Holds the current round number */ 44 | private int nRoundNumber; 45 | 46 | /** A text field showing the current round number */ 47 | private JTextField roundNumber; 48 | 49 | // Debugger 50 | private JLabel addressFiled; 51 | private JButton btnCpuState; 52 | private CpuFrame cpuFrame; 53 | private JButton btnPause; 54 | private JButton btnSingleRound; 55 | 56 | 57 | private JSlider speedSlider; 58 | 59 | private final Competition competition; 60 | 61 | private MemoryFrame memoryFrame; 62 | 63 | public WarFrame(final Competition competition) { 64 | super("CodeGuru Extreme - Session Viewer"); 65 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 66 | this.competition = competition; 67 | getContentPane().setLayout(new BorderLayout()); 68 | 69 | // build widgets 70 | JPanel mainPanel = new JPanel(new BorderLayout()); 71 | 72 | // build war zone (canvas + title) 73 | JPanel warZone = new JPanel(new BorderLayout()); 74 | warZone.setBackground(Color.BLACK); 75 | 76 | JPanel canvasPanel = new JPanel(); 77 | canvasPanel.setBorder(BorderFactory.createCompoundBorder( 78 | BorderFactory.createLineBorder(new Color(169,154,133),3), 79 | BorderFactory.createEmptyBorder(10,10,20,10))); 80 | canvasPanel.setBackground(Color.BLACK); 81 | warCanvas = new Canvas(); 82 | canvasPanel.add(warCanvas); 83 | warZone.add(canvasPanel, BorderLayout.CENTER); 84 | 85 | //warZone.add(new JLabel(new ImageIcon("images/warzone.jpg")), BorderLayout.NORTH); 86 | mainPanel.add(warZone, BorderLayout.CENTER); 87 | 88 | // build info zone (message area + buttons) 89 | JPanel infoZone = new JPanel(new BorderLayout()); 90 | messagesArea = new JTextArea(5, 60); 91 | messagesArea.setFont(new Font("Tahoma", Font.PLAIN, 12)); 92 | 93 | infoZone.add(new JScrollPane(messagesArea), BorderLayout.CENTER); 94 | 95 | JPanel buttonPanel = new JPanel(); 96 | 97 | buttonPanel.add(new JLabel("Round:")); 98 | roundNumber = new JTextField(4); 99 | roundNumber.setEditable(false); 100 | buttonPanel.add(roundNumber); 101 | buttonPanel.add(Box.createHorizontalStrut(20)); 102 | JButton closeButton = new JButton("Close"); 103 | closeButton.addActionListener(e -> dispose()); 104 | buttonPanel.add(closeButton); 105 | buttonPanel.add(Box.createHorizontalStrut(20)); 106 | buttonPanel.add(new JLabel("Speed:")); 107 | speedSlider = new JSlider(1,100,competition.getSpeed()); 108 | speedSlider.addChangeListener(e -> { 109 | WarFrame.this.competition.setSpeed((int) Math.pow(1.2, speedSlider.getValue()) ); //exponential speed slider 110 | }); 111 | buttonPanel.add(speedSlider); 112 | nRoundNumber = 0; 113 | infoZone.add(buttonPanel, BorderLayout.SOUTH); 114 | infoZone.setBackground(Color.black); 115 | 116 | // Debugger 117 | addressFiled = new JLabel("Click on the arena to see the memory"); 118 | warCanvas.addListener(this); 119 | 120 | btnCpuState = new JButton("View CPU"); 121 | btnCpuState.setEnabled(false); 122 | btnCpuState.addActionListener(event -> { 123 | cpuFrame = new CpuFrame(competition); 124 | WarFrame.this.competition.addCompetitionEventListener(cpuFrame); 125 | }); 126 | 127 | competition.addCompetitionEventListener(this); 128 | 129 | btnPause = new JButton("Pause"); 130 | btnPause.setEnabled(false); 131 | btnPause.addActionListener(event -> { 132 | if (competition.getCurrentWar().isPaused()) { 133 | competition.getCurrentWar().resume(); 134 | btnPause.setText("Pause"); 135 | btnSingleRound.setEnabled(false); 136 | } else { 137 | competition.getCurrentWar().pause(); 138 | btnPause.setText("Resume"); 139 | btnSingleRound.setEnabled(true); 140 | } 141 | 142 | }); 143 | 144 | btnSingleRound = new JButton("Single Round"); 145 | btnSingleRound.setEnabled(false); 146 | btnSingleRound.addActionListener(event -> competition.getCurrentWar().runSingleRound()); 147 | 148 | buttonPanel.add(btnCpuState); 149 | buttonPanel.add(btnPause); 150 | buttonPanel.add(btnSingleRound); 151 | buttonPanel.add(addressFiled); 152 | 153 | // build warrior zone (warrior list + title) 154 | JPanel warriorZone = new JPanel(new BorderLayout()); 155 | warriorZone.setBackground(Color.BLACK); 156 | nameListModel = new DefaultListModel(); 157 | nameList = new JList(nameListModel); 158 | nameList.setPreferredSize(new Dimension(200,0)); 159 | nameList.setCellRenderer(new NameCellRenderer()); 160 | nameList.setOpaque(false); 161 | nameList.setBorder(BorderFactory.createCompoundBorder( 162 | BorderFactory.createLineBorder(new Color(169,154,133),3), 163 | BorderFactory.createEmptyBorder(10,10,20,10))); 164 | nameList.repaint(); 165 | warriorZone.add(nameList, BorderLayout.CENTER); 166 | //warriorZone.add(new JLabel(new ImageIcon("images/warriors.jpg")), BorderLayout.NORTH); 167 | warriorZone.add(Box.createHorizontalStrut(20), BorderLayout.WEST); 168 | mainPanel.add(warriorZone, BorderLayout.EAST); 169 | 170 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 171 | getContentPane().setBackground(Color.BLACK); 172 | getContentPane().add(mainPanel, BorderLayout.CENTER); 173 | //getContentPane().add(new JLabel(new ImageIcon("images/title2.png")), BorderLayout.EAST); 174 | getContentPane().add(infoZone, BorderLayout.SOUTH); 175 | } 176 | 177 | /** Add a message to the message zone */ 178 | public void addMessage(String message) { 179 | messagesArea.append(message + "\n"); 180 | SwingUtilities.invokeLater(new Runnable() { 181 | public void run() { 182 | messagesArea.setCaretPosition(messagesArea.getDocument().getLength()); 183 | } 184 | }); 185 | } 186 | 187 | /** Add a message to the message zone (with round number) */ 188 | public void addMessage(int round, String message) { 189 | addMessage("[" + round + "] "+ message); 190 | } 191 | 192 | /** @see MemoryEventListener#onMemoryWrite(RealModeAddress) */ 193 | public void onMemoryWrite(RealModeAddress address) { 194 | int ipInsideArena = address.getLinearAddress() - 0x1000 *0x10; // arena * paragraph 195 | 196 | if ( address.getLinearAddress() >= War.ARENA_SEGMENT*0x10 && address.getLinearAddress() < 2*War.ARENA_SEGMENT*0x10 ) { 197 | warCanvas.paintPixel( 198 | Unsigned.unsignedShort(ipInsideArena), 199 | (byte)competition.getCurrentWarrior()); 200 | } 201 | } 202 | 203 | /** @see CompetitionEventListener#onWarStart(long) */ 204 | public void onWarStart(long seed) { 205 | addMessage("=== Session started ==="); 206 | nameListModel.clear(); 207 | warCanvas.clear(); 208 | if (competition.getCurrentWar().isPaused()){ 209 | btnPause.setText("Resume"); 210 | btnSingleRound.setEnabled(true); 211 | } 212 | } 213 | 214 | /** @see CompetitionEventListener#onWarEnd(int, String) */ 215 | public void onWarEnd(int reason, String winners) { 216 | roundNumber.setText(Integer.toString(nRoundNumber)); 217 | roundNumber.repaint(); 218 | 219 | switch (reason) { 220 | case SINGLE_WINNER: 221 | addMessage(nRoundNumber, 222 | "Session over: The winner is " + winners + "!"); 223 | break; 224 | case MAX_ROUND_REACHED: 225 | addMessage(nRoundNumber, 226 | "Maximum round reached: The winners are " + winners + "!"); 227 | break; 228 | case ABORTED: 229 | addMessage(nRoundNumber, 230 | "Session aborted: The winners are " + winners + "!"); 231 | break; 232 | default: 233 | throw new RuntimeException(); 234 | } 235 | } 236 | 237 | /** @see CompetitionEventListener#onRound(int) */ 238 | public void onRound(int round) { 239 | nRoundNumber = round; 240 | if ((nRoundNumber % 1000) == 0) { 241 | roundNumber.setText(Integer.toString(nRoundNumber)); 242 | roundNumber.repaint(); 243 | } 244 | btnCpuState.setEnabled(true); //in case we open the window during a match 245 | btnPause.setEnabled(true); 246 | } 247 | 248 | /** @see CompetitionEventListener#onWarriorBirth(String) */ 249 | public void onWarriorBirth(String warriorName) { 250 | addMessage(nRoundNumber, warriorName + " enters the arena."); 251 | nameListModel.addElement(new WarriorInfo(warriorName)); 252 | } 253 | 254 | /** @see CompetitionEventListener#onWarriorDeath(String) */ 255 | public void onWarriorDeath(String warriorName, String reason) { 256 | addMessage(nRoundNumber, warriorName + " died due to " + reason + "."); 257 | Enumeration namesListElements = nameListModel.elements(); 258 | while(namesListElements.hasMoreElements()) { 259 | WarriorInfo info = (WarriorInfo) namesListElements.nextElement(); 260 | if (info.name.equals(warriorName)) { 261 | info.alive = false; 262 | break; 263 | } 264 | } 265 | 266 | // a bit bogus... just to make the list refresh and show the new status. 267 | SwingUtilities.invokeLater(new Runnable() { 268 | public void run() { 269 | nameList.repaint(); 270 | } 271 | }); 272 | } 273 | 274 | /** 275 | * A renderer for the names on the warrior list. 276 | * Paints each warrior with its color and uses strikeout to show 277 | * dead warriors. 278 | */ 279 | class NameCellRenderer extends JLabel implements ListCellRenderer { 280 | private static final long serialVersionUID = 1L; 281 | 282 | private static final int FONT_SIZE = 20; 283 | 284 | /** 285 | * Construct a name cell renderer 286 | * Set font size to FONT_SIZE. 287 | */ 288 | public NameCellRenderer() { 289 | setFont(new Font("Tahoma", Font.PLAIN, FONT_SIZE)); 290 | } 291 | 292 | /** 293 | * @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean) 294 | */ 295 | public Component getListCellRendererComponent(JList list, Object value, 296 | int index, boolean isSelected, boolean cellHasFocus) { 297 | WarriorInfo info = (WarriorInfo)value; 298 | /* 299 | float warriorScore = m_warSession.m_scoreBoard.getScore(warriorName); 300 | warriorScore = (float)((int)(warriorScore * 100)) / 100; 301 | */ 302 | String text = info.name;// + " (" + warriorScore + ")"; 303 | if (!info.alive) { 304 | // strike out dead warriors 305 | text = "" + text + ""; 306 | } 307 | setText(text); 308 | setForeground(warCanvas.getColorForWarrior(index)); 309 | return this; 310 | } 311 | } 312 | 313 | public void onCompetitionStart() { 314 | btnCpuState.setEnabled(true); 315 | btnPause.setEnabled(true); 316 | } 317 | 318 | public void onCompetitionEnd() { 319 | btnCpuState.setEnabled(false); 320 | btnPause.setEnabled(false); 321 | } 322 | 323 | class WarriorInfo { 324 | String name; 325 | boolean alive; 326 | 327 | public WarriorInfo(String name) { 328 | this.name= name; 329 | this.alive = true; 330 | } 331 | 332 | @Override 333 | public String toString() { 334 | return name; 335 | } 336 | 337 | @Override 338 | public boolean equals(Object obj) { 339 | return (obj!=null) && (obj instanceof String) && 340 | (((String)obj).equals(name)); 341 | } 342 | } 343 | 344 | @Override 345 | public void onEndRound() { 346 | this.warCanvas.deletePointers(); 347 | for (int i = 0; i < this.competition.getCurrentWar().getNumWarriors(); i++) 348 | if (this.competition.getCurrentWar().getWarrior(i).isAlive()) { 349 | short ip = this.competition.getCurrentWar().getWarrior(i).getCpuState().getIP(); 350 | short cs = this.competition.getCurrentWar().getWarrior(i).getCpuState().getCS(); 351 | 352 | int ipInsideArena = new RealModeAddress(cs, ip).getLinearAddress() - 0x10000; 353 | 354 | this.warCanvas.paintPointer((char) ipInsideArena,(byte) i); 355 | } 356 | } 357 | 358 | @Override 359 | public void dispose() { 360 | 361 | // bug fix - event casted while window is being disposed FIXME find a 362 | // better solution 363 | this.competition.getCurrentWar().pause(); 364 | try { 365 | Thread.sleep(300); 366 | } catch (Exception e) { 367 | 368 | } 369 | this.competition.removeCompetitionEventListener(this); 370 | this.competition.removeMemoryEventLister(this); 371 | this.competition.getCurrentWar().resume(); 372 | 373 | try { 374 | this.cpuFrame.dispose(); 375 | } catch (Exception e) { 376 | } 377 | // restoring maximum speed 378 | competition.getCurrentWar().resume(); 379 | competition.setSpeed(Competition.MAXIMUM_SPEED); 380 | super.dispose(); 381 | } 382 | 383 | @Override 384 | public void addressAtMouseLocationRequested(int address) { 385 | RealModeAddress tmp = new RealModeAddress(War.ARENA_SEGMENT, (short) address); 386 | byte data = this.competition.getCurrentWar().getMemory().readByte(tmp); 387 | 388 | // Warrior w = this.competition.getCurrentWar().getNumWarriors() 389 | 390 | this.addressFiled.setText(Integer.toHexString(address).toUpperCase() 391 | + ": " + String.format("%02X", data).toUpperCase()); 392 | 393 | if (memoryFrame == null || !memoryFrame.isVisible()) { 394 | memoryFrame = new MemoryFrame(competition, tmp.getLinearAddress()); 395 | this.competition.addCompetitionEventListener(memoryFrame); 396 | } 397 | else 398 | memoryFrame.refresh(tmp.getLinearAddress()); 399 | } 400 | 401 | } 402 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/AbstractRealModeMemory.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | import il.co.codeguru.corewars8086.utils.Unsigned; 4 | 5 | /** 6 | * Base class for classes implementing the RealModeMemory interface, which 7 | * provides simple implementation of the 'word' methods using the 'byte' methods. 8 | * 9 | * @author DL 10 | */ 11 | public abstract class AbstractRealModeMemory implements RealModeMemory { 12 | 13 | /** 14 | * Reads a single byte from the specified address. 15 | * 16 | * @param address Real-mode address to read from. 17 | * @return the read byte. 18 | * 19 | * @throws MemoryException on any error. 20 | */ 21 | public abstract byte readByte(RealModeAddress address) throws MemoryException; 22 | 23 | /** 24 | * Reads a single word from the specified address. 25 | * 26 | * @param address Real-mode address to read from. 27 | * @return the read word. 28 | * 29 | * @throws MemoryException on any error. 30 | */ 31 | public short readWord(RealModeAddress address) throws MemoryException { 32 | // read low word 33 | byte low = readByte(address); 34 | 35 | // read high word 36 | RealModeAddress nextAddress = new RealModeAddress( 37 | address.getSegment(), (short)(address.getOffset() + 1)); 38 | byte high = readByte(nextAddress); 39 | 40 | return (short)((Unsigned.unsignedByte(high) << 8) | 41 | Unsigned.unsignedByte(low)); 42 | } 43 | 44 | /** 45 | * Writes a single byte to the specified address. 46 | * 47 | * @param address Real-mode address to write to. 48 | * @param value Data to write. 49 | * 50 | * @throws MemoryException on any error. 51 | */ 52 | public abstract void writeByte(RealModeAddress address, byte value) 53 | throws MemoryException; 54 | 55 | /** 56 | * Writes a single word to the specified address. 57 | * 58 | * @param address Real-mode address to write to. 59 | * @param value Data to write. 60 | * 61 | * @throws MemoryException on any error. 62 | */ 63 | public void writeWord(RealModeAddress address, short value) 64 | throws MemoryException { 65 | 66 | byte low = (byte)value; 67 | byte high = (byte)(value >> 8); 68 | 69 | // write low byte 70 | writeByte(address, low); 71 | 72 | // write high byte 73 | RealModeAddress nextAddress = new RealModeAddress( 74 | address.getSegment(), (short)(address.getOffset() + 1)); 75 | writeByte(nextAddress, high); 76 | } 77 | 78 | /** 79 | * Reads a single byte from the specified address, in order to execute it. 80 | * 81 | * @param address Real-mode address to read from. 82 | * @return the read byte. 83 | * 84 | * @throws MemoryException on any error. 85 | */ 86 | public abstract byte readExecuteByte(RealModeAddress address) 87 | throws MemoryException; 88 | 89 | /** 90 | * Reads a single word from the specified address, in order to execute it. 91 | * 92 | * @param address Real-mode address to read from. 93 | * @return the read word. 94 | * 95 | * @throws MemoryException on any error. 96 | */ 97 | public short readExecuteWord(RealModeAddress address) throws MemoryException { 98 | // read low word 99 | byte low = readExecuteByte(address); 100 | 101 | // read high word 102 | RealModeAddress nextAddress = new RealModeAddress( 103 | address.getSegment(), (short)(address.getOffset() + 1)); 104 | byte high = readExecuteByte(nextAddress); 105 | 106 | return (short)((Unsigned.unsignedByte(high) << 8) | 107 | Unsigned.unsignedByte(low)); 108 | } 109 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/MemoryEventListener.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | import java.util.EventListener; 4 | 5 | /** 6 | * Defines an interface for memory listeners 7 | * 8 | * @author BS 9 | */ 10 | public interface MemoryEventListener extends EventListener { 11 | /** 12 | * Called when a byte is written to memory 13 | * @param address 14 | */ 15 | void onMemoryWrite(RealModeAddress address); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/MemoryException.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | /** 4 | * Base class for all Exceptions thrown by the RealModeMemory classes. 5 | * 6 | * @author DL 7 | */ 8 | public class MemoryException extends Exception { 9 | private static final long serialVersionUID = 1L; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/RealModeAddress.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | import il.co.codeguru.corewars8086.utils.Unsigned; 4 | 5 | /** 6 | * Wrapper class for a Real-Mode segment:offset address. 7 | * 8 | * @author DL 9 | */ 10 | public class RealModeAddress { 11 | 12 | /** 13 | * Constructor from segment:offset. 14 | * 15 | * @param segment 16bit Real-mode segment. 16 | * @param offset 16bit Real-mode offset. 17 | */ 18 | public RealModeAddress(short segment, short offset) { 19 | m_segment = segment; 20 | m_offset = offset; 21 | 22 | int unsignedSegment = Unsigned.unsignedShort(m_segment); 23 | int unsignedOffset = Unsigned.unsignedShort(m_offset); 24 | 25 | int linearAddressFull = unsignedSegment * PARAGRAPH_SIZE + unsignedOffset; 26 | m_linearAddress = linearAddressFull % MEMORY_SIZE; 27 | } 28 | 29 | /** 30 | * Constructor from linear address. 31 | * 32 | * The 'segment' part will be the highest possible, e.g.: 33 | * 12345h -> 1234:0005h 34 | * 35 | * @param linearAddress 32bit linear address. 36 | */ 37 | public RealModeAddress(int linearAddress) { 38 | linearAddress %= MEMORY_SIZE; 39 | 40 | int unsignedSegment = Unsigned.unsignedShort(linearAddress / PARAGRAPH_SIZE); 41 | int unsignedOffset = Unsigned.unsignedShort( 42 | (linearAddress - (unsignedSegment*PARAGRAPH_SIZE))); 43 | 44 | m_segment = (short)unsignedSegment; 45 | m_offset = (short)unsignedOffset; 46 | 47 | m_linearAddress = linearAddress; 48 | } 49 | 50 | /** 51 | * @return 16bit Real-Mode segment. 52 | */ 53 | public short getSegment() { 54 | return m_segment; 55 | } 56 | 57 | /** 58 | * @return 16bit Real-Mode offset. 59 | */ 60 | public short getOffset() { 61 | return m_offset; 62 | } 63 | 64 | /** 65 | * @return 32bit linear address. 66 | */ 67 | public int getLinearAddress() { 68 | return m_linearAddress; 69 | } 70 | 71 | /** Various real-mode memory constants. */ 72 | public static final int NUM_PARAGRAPHS = 64 * 1024; 73 | public static final int PARAGRAPH_SIZE = 0x10; 74 | public static final int PARAGRAPHS_IN_SEGMENT = 0x1000; 75 | public static final int MEMORY_SIZE = NUM_PARAGRAPHS * PARAGRAPH_SIZE; 76 | 77 | /** 16bit Real-Mode segment. */ 78 | private final short m_segment; 79 | 80 | /** 16bit Real-Mode offset. */ 81 | private final short m_offset; 82 | 83 | /** cached linear representation of segment and offset */ 84 | private int m_linearAddress; 85 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/RealModeMemory.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | /** 4 | * Interface for 16bit Real-Mode memory. 5 | * 6 | * @author DL 7 | */ 8 | public interface RealModeMemory { 9 | 10 | /** 11 | * Reads a single byte from the specified address. 12 | * 13 | * @param address Real-mode address to read from. 14 | * @return the read byte. 15 | * 16 | * @throws MemoryException on any error. 17 | */ 18 | public abstract byte readByte(RealModeAddress address) throws MemoryException; 19 | 20 | /** 21 | * Reads a single word from the specified address. 22 | * 23 | * @param address Real-mode address to read from. 24 | * @return the read word. 25 | * 26 | * @throws MemoryException on any error. 27 | */ 28 | public abstract short readWord(RealModeAddress address) throws MemoryException; 29 | 30 | /** 31 | * Writes a single byte to the specified address. 32 | * 33 | * @param address Real-mode address to write to. 34 | * @param value Data to write. 35 | * 36 | * @throws MemoryException on any error. 37 | */ 38 | public abstract void writeByte(RealModeAddress address, byte value) 39 | throws MemoryException; 40 | 41 | /** 42 | * Writes a single word to the specified address. 43 | * 44 | * @param address Real-mode address to write to. 45 | * @param value Data to write. 46 | * 47 | * @throws MemoryException on any error. 48 | */ 49 | public abstract void writeWord(RealModeAddress address, short value) 50 | throws MemoryException; 51 | 52 | /** 53 | * Reads a single byte from the specified address, in order to execute it. 54 | * 55 | * @param address Real-mode address to read from. 56 | * @return the read byte. 57 | * 58 | * @throws MemoryException on any error. 59 | */ 60 | public abstract byte readExecuteByte(RealModeAddress address) 61 | throws MemoryException; 62 | 63 | /** 64 | * Reads a single word from the specified address, in order to execute it. 65 | * 66 | * @param address Real-mode address to read from. 67 | * @return the read word. 68 | * 69 | * @throws MemoryException on any error. 70 | */ 71 | public abstract short readExecuteWord(RealModeAddress address) 72 | throws MemoryException; 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/RealModeMemoryImpl.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | /** 4 | * Implements the RealModeMemory interface using a buffer. 5 | * 6 | * @author DL 7 | */ 8 | public class RealModeMemoryImpl extends AbstractRealModeMemory { 9 | 10 | /** Listener to memory events */ 11 | private MemoryEventListener listener; 12 | 13 | /** Actual memory data */ 14 | private byte[] m_data; 15 | 16 | /** 17 | * Constructor. 18 | */ 19 | public RealModeMemoryImpl() { 20 | m_data = new byte[RealModeAddress.MEMORY_SIZE]; 21 | } 22 | 23 | /** 24 | * Reads a single byte from the specified address. 25 | * 26 | * @param address Real-mode address to read from. 27 | * @return the read byte. 28 | * 29 | * @throws MemoryException on any error. 30 | */ 31 | public byte readByte(RealModeAddress address) { 32 | return m_data[address.getLinearAddress()]; 33 | } 34 | 35 | /** 36 | * Writes a single byte to the specified address. 37 | * 38 | * @param address Real-mode address to write to. 39 | * @param value Data to write. 40 | * 41 | * @throws MemoryException on any error. 42 | */ 43 | public void writeByte(RealModeAddress address, byte value) { 44 | m_data[address.getLinearAddress()] = value; 45 | if (listener != null) { 46 | listener.onMemoryWrite(address); 47 | } 48 | } 49 | 50 | /** 51 | * Reads a single byte from the specified address, in order to execute it. 52 | * 53 | * @param address Real-mode address to read from. 54 | * @return the read byte. 55 | * 56 | * @throws MemoryException on any error. 57 | */ 58 | public byte readExecuteByte(RealModeAddress address) { 59 | return m_data[address.getLinearAddress()]; 60 | } 61 | 62 | /** 63 | * @return Returns the listener. 64 | */ 65 | public MemoryEventListener getListener() { 66 | return listener; 67 | } 68 | /** 69 | * @param listener The listener to set. 70 | */ 71 | public void setListener(MemoryEventListener listener) { 72 | this.listener = listener; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/RealModeMemoryRegion.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | /** 4 | * Memory region (start address, end address) 5 | * 6 | * @author DL 7 | */ 8 | public class RealModeMemoryRegion { 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param start Region's start address. 14 | * @param end Region's end address. 15 | */ 16 | public RealModeMemoryRegion(RealModeAddress start, RealModeAddress end) { 17 | m_start = start; 18 | m_end = end; 19 | } 20 | 21 | /** 22 | * Returns whether or not a given address is within the region. 23 | * 24 | * @param address Address to check. 25 | * @return whether or not the given address is within the region. 26 | */ 27 | public boolean isInRegion(RealModeAddress address) { 28 | final int start = m_start.getLinearAddress(); 29 | final int end = m_end.getLinearAddress(); 30 | final int asked = address.getLinearAddress(); 31 | 32 | return ((asked >= start) && (asked <= end)); 33 | } 34 | 35 | /** Region's start address */ 36 | private final RealModeAddress m_start; 37 | /** Region's end address */ 38 | private final RealModeAddress m_end; 39 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/memory/RestrictedAccessRealModeMemory.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.memory; 2 | 3 | /** 4 | * Implementation of the RealModeMemory interface which limits memory access 5 | * to given regions of the memory. 6 | * 7 | * @author DL 8 | */ 9 | public class RestrictedAccessRealModeMemory extends AbstractRealModeMemory { 10 | 11 | /** 12 | * Constructor. 13 | * 14 | * @param memory Wrapped RealModeMemory implementation. 15 | * @param readAccessRegions Reading from these regions is allowed. 16 | * @param writeAccessRegions Writing to these regions is allowed. 17 | * @param executeAccessRegions Executing these regions is allowed. 18 | */ 19 | public RestrictedAccessRealModeMemory( 20 | RealModeMemory memory, 21 | RealModeMemoryRegion[] readAccessRegions, 22 | RealModeMemoryRegion[] writeAccessRegions, 23 | RealModeMemoryRegion[] executeAccessRegions) { 24 | 25 | m_memory = memory; 26 | m_readAccessRegions = readAccessRegions; 27 | m_writeAccessRegions = writeAccessRegions; 28 | m_executeAccessRegions = executeAccessRegions; 29 | } 30 | 31 | /** 32 | * Reads a single byte from the specified address. 33 | * 34 | * @param address Real-mode address to read from. 35 | * @return the read byte. 36 | * 37 | * @throws MemoryException if reading is not allowed from this address. 38 | */ 39 | public byte readByte(RealModeAddress address) throws MemoryException { 40 | // is reading allowed from this address ? 41 | if (!isAddressInRegions(m_readAccessRegions, address)) { 42 | throw new MemoryException(); 43 | } 44 | 45 | return m_memory.readByte(address); 46 | } 47 | 48 | /** 49 | * Writes a single byte to the specified address. 50 | * 51 | * @param address Real-mode address to write to. 52 | * @param value Data to write. 53 | * 54 | * @throws MemoryException if writing is not allowed to this address. 55 | */ 56 | public void writeByte(RealModeAddress address, byte value) throws MemoryException { 57 | // is writing allowed to this address ? 58 | if (!isAddressInRegions(m_writeAccessRegions, address)) { 59 | throw new MemoryException(); 60 | } 61 | 62 | m_memory.writeByte(address, value); 63 | } 64 | 65 | /** 66 | * Reads a single byte from the specified address, in order to execute it. 67 | * 68 | * @param address Real-mode address to read from. 69 | * @return the read byte. 70 | * 71 | * @throws MemoryException if reading is not allowed from this address. 72 | */ 73 | public byte readExecuteByte(RealModeAddress address) throws MemoryException { 74 | // is reading allowed from this address ? 75 | if (!isAddressInRegions(m_executeAccessRegions, address)) { 76 | throw new MemoryException(); 77 | } 78 | 79 | return m_memory.readExecuteByte(address); 80 | } 81 | 82 | /** 83 | * Checks whether or not a given address is within at least a single 84 | * region in an array of regions. 85 | * 86 | * @param regions Regions array to match address against. 87 | * @param address Address to check. 88 | * @return whether or not the address is within at least one of the regions. 89 | */ 90 | private boolean isAddressInRegions( 91 | RealModeMemoryRegion[] regions, RealModeAddress address) { 92 | 93 | // iterate all regions, attempt to match address 94 | boolean found = false; 95 | for (int i = 0; i < regions.length; ++i) { 96 | if (regions[i].isInRegion(address)) { 97 | found = true; 98 | break; 99 | } 100 | } 101 | 102 | return found; 103 | } 104 | 105 | /** Wrapped RealModeMemory implementation */ 106 | private final RealModeMemory m_memory; 107 | /** Reading from these regions is allowed */ 108 | private final RealModeMemoryRegion[] m_readAccessRegions; 109 | /** Writing to these regions is allowed */ 110 | private final RealModeMemoryRegion[] m_writeAccessRegions; 111 | /** Executing these regions is allowed */ 112 | private final RealModeMemoryRegion[] m_executeAccessRegions; 113 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/utils/Disassembler.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.utils; 2 | 3 | import java.io.BufferedOutputStream; 4 | import java.io.BufferedReader; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.nio.file.Files; 11 | 12 | public class Disassembler { 13 | 14 | public static String disassembler(byte[] bytes) throws Exception { 15 | 16 | File root = new File(Disassembler.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile(); 17 | File tempfile = new File( root + "\\temp_disassemblr"); 18 | BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(tempfile.toPath())); 19 | bos.write(bytes); 20 | bos.flush(); 21 | bos.close(); 22 | 23 | ProcessBuilder pb = new ProcessBuilder("ndisasm", "-b 16","-pintel" , tempfile + ""); 24 | pb.redirectOutput(); 25 | Process p = pb.start(); 26 | 27 | String error = getStringFromInput(p.getErrorStream()); 28 | if(error == null) 29 | throw new Exception(error); 30 | 31 | String result = getStringFromInput(p.getInputStream()); 32 | 33 | StringBuilder sb = new StringBuilder(); 34 | String[] lines = result.split("\n"); 35 | for (String line : lines) { 36 | String args[] = line.split("\\s\\s+"); 37 | StringBuffer opcode = new StringBuffer(args[1]); 38 | while(opcode.length() < 10) opcode.append(" "); 39 | sb.append(opcode).append(args[2]).append("\n"); 40 | 41 | } 42 | 43 | return sb.toString(); 44 | } 45 | 46 | private static String getStringFromInput(InputStream s) throws IOException { 47 | BufferedReader br = new BufferedReader(new InputStreamReader(s)); 48 | StringBuilder builder = new StringBuilder(); 49 | String line = null; 50 | while ( (line = br.readLine()) != null) { 51 | builder.append(line); 52 | builder.append(System.getProperty("line.separator")); 53 | } 54 | String result = builder.toString(); 55 | return result; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/utils/EventMulticaster.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.utils; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | import java.util.*; 7 | 8 | /** 9 | * An event multicaster which broadcasts Events to a number of listeners. 10 | * @author BS 11 | */ 12 | public class EventMulticaster { 13 | 14 | private Class mListenerInterface; 15 | private EventListener mProxy; 16 | private Set mListeners = new LinkedHashSet<>(); 17 | private EventListener[] mListenersArr = new EventListener[0]; 18 | private Set mWaitingListeners = new LinkedHashSet(); 19 | private boolean isCasting; 20 | 21 | /** Construct a new EventMulticaster for the given listener class. 22 | */ 23 | public EventMulticaster(Class pListenerInterface) { 24 | if (! EventListener.class.isAssignableFrom(pListenerInterface) ) { 25 | throw new IllegalArgumentException("Listener interface must extend java.util.EventListener."); 26 | } 27 | mListenerInterface = pListenerInterface; 28 | } 29 | 30 | /** Add an event listener to the list. 31 | */ 32 | public void add(EventListener pListener) { 33 | if (isCasting) { 34 | mWaitingListeners .add(pListener); 35 | } else { 36 | mListeners.add(pListener); 37 | } 38 | mListenersArr = mListeners.toArray(new EventListener[mListeners.size()]); 39 | } 40 | 41 | /** Remove an event listener from the list. 42 | */ 43 | public void remove(EventListener pListener) { 44 | mListeners.remove(pListener); 45 | mListenersArr = mListeners.toArray(new EventListener[mListeners.size()]); 46 | } 47 | 48 | /** Get the proxy for this event multicaster. This proxy can then be 49 | * used to broadcast events to all the registered event listeners. 50 | */ 51 | public synchronized EventListener getProxy() { 52 | if ( mProxy == null ) { 53 | ClassLoader lCL = this.getClass().getClassLoader(); 54 | Class[] lClasses = new Class[] {mListenerInterface}; 55 | mProxy = (EventListener)Proxy.newProxyInstance(lCL,lClasses,new MulticasterInvocationHandler()); 56 | } 57 | return mProxy; 58 | } 59 | 60 | /** Invokes the given method on each listener registered with the 61 | * multicaster. 62 | */ 63 | private class MulticasterInvocationHandler implements InvocationHandler { 64 | public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) 65 | throws Throwable { 66 | 67 | isCasting = true; 68 | for (EventListener mListener : mListenersArr) { 69 | pMethod.invoke(mListener, pArgs); 70 | } 71 | isCasting = false; 72 | if (!mWaitingListeners.isEmpty()) { 73 | // listeners are waiting to be added, add them after multicasting 74 | for (EventListener lis: mWaitingListeners) { 75 | add(lis); 76 | } 77 | mWaitingListeners.clear(); 78 | } 79 | return null; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/utils/Unsigned.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.utils; 2 | 3 | /** 4 | * Unsigned numbers utilities. 5 | * 6 | * @author DL 7 | */ 8 | public class Unsigned { 9 | 10 | public static short unsignedByte(byte num) { 11 | return (short)((short)num & 0xFF); 12 | } 13 | 14 | public static int unsignedShort(short num) { 15 | return unsignedShort((int)num); 16 | } 17 | 18 | public static int unsignedShort(int num) { 19 | return (num & 0xFFFF); 20 | } 21 | 22 | public static long unsignedInt(int num) { 23 | return ((long)num & 0xFFFFFFFF); 24 | } 25 | 26 | /** 27 | * Private constructor. 28 | */ 29 | private Unsigned() {} 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/Competition.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | import il.co.codeguru.corewars8086.memory.MemoryEventListener; 5 | import il.co.codeguru.corewars8086.utils.EventMulticaster; 6 | 7 | import java.io.IOException; 8 | import java.util.Optional; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | 14 | public class Competition { 15 | 16 | /** Maximum number of rounds in a single war. */ 17 | public final static int MAX_ROUND = 200000; 18 | private static final String SCORE_FILENAME= "scores.csv"; 19 | 20 | private CompetitionIterator competitionIterator; 21 | 22 | private EventMulticaster competitionEventCaster, memoryEventCaster; 23 | private CompetitionEventListener competitionEventListener; 24 | private MemoryEventListener memoryEventListener; 25 | 26 | private final WarriorRepository warriorRepository; 27 | 28 | private ExecutorService executorService; 29 | 30 | private War currentWar; 31 | 32 | private int warsPerCombination= 20; 33 | 34 | private int speed; 35 | public static final int MAXIMUM_SPEED = -1; 36 | private static final long DELAY_UNIT = 200; 37 | 38 | private long seed = 0; 39 | 40 | private boolean abort; 41 | 42 | private final Options options; 43 | 44 | public Competition(Options options) throws IOException { 45 | this(true, options); 46 | } 47 | 48 | public Competition(boolean shouldReadWarriorsFile, Options options) throws IOException { 49 | warriorRepository = new WarriorRepository(shouldReadWarriorsFile, options); 50 | 51 | competitionEventCaster = new EventMulticaster(CompetitionEventListener.class); 52 | competitionEventListener = (CompetitionEventListener) competitionEventCaster.getProxy(); 53 | memoryEventCaster = new EventMulticaster(MemoryEventListener.class); 54 | memoryEventListener = (MemoryEventListener) memoryEventCaster.getProxy(); 55 | speed = MAXIMUM_SPEED; 56 | abort = false; 57 | 58 | this.options = options; 59 | } 60 | 61 | public void runCompetition (int warsPerCombination, int warriorsPerGroup, boolean startPaused) throws Exception { 62 | this.warsPerCombination = warsPerCombination; 63 | competitionIterator = new CompetitionIterator( 64 | warriorRepository.getNumberOfGroups(), warriorsPerGroup); 65 | 66 | // run on every possible combination of warrior groups 67 | competitionEventListener.onCompetitionStart(); 68 | for (int warCount = 0; warCount < getTotalNumberOfWars(); warCount++) { 69 | runWar(warriorRepository.createGroupList(competitionIterator.next()), startPaused); 70 | seed ++; 71 | if (abort) { 72 | break; 73 | } 74 | } 75 | competitionEventListener.onCompetitionEnd(); 76 | warriorRepository.saveScoresToFile(SCORE_FILENAME); 77 | } 78 | 79 | public void runCompetitionInParallel(int warsPerCombination, int warriorsPerGroup, int threads) throws InterruptedException { 80 | this.warsPerCombination = warsPerCombination; 81 | competitionIterator = new CompetitionIterator(warriorRepository.getNumberOfGroups(), warriorsPerGroup); 82 | competitionEventListener.onCompetitionStart(); 83 | 84 | executorService = Executors.newFixedThreadPool(threads); 85 | 86 | for (int warCount = 0; warCount < getTotalNumberOfWars(); warCount++) { 87 | WarriorGroup[] groups = warriorRepository.createGroupList(competitionIterator.next()); 88 | int id = warCount; 89 | long warSeed = seed++; 90 | executorService.submit(() -> { 91 | try { 92 | runWarInParallel(groups, warSeed, id); 93 | } catch (Exception e) { 94 | throw new RuntimeException(e); 95 | } 96 | }); 97 | } 98 | 99 | executorService.shutdown(); 100 | boolean finished = executorService.awaitTermination(1, TimeUnit.HOURS); 101 | 102 | if (!finished) { 103 | System.err.println("Note: Competition has timed out after 1h - results may be incorrect."); 104 | } 105 | 106 | executorService = null; 107 | 108 | competitionEventListener.onCompetitionEnd(); 109 | warriorRepository.saveScoresToFile(options.outputFile); 110 | } 111 | 112 | public int getTotalNumberOfWars() { 113 | return (int) competitionIterator.getNumberOfItems() * warsPerCombination; 114 | } 115 | 116 | public void runWar(WarriorGroup[] warriorGroups,boolean startPaused) throws Exception { 117 | currentWar = new War(memoryEventListener, competitionEventListener, startPaused, options); 118 | currentWar.setSeed(this.seed); 119 | competitionEventListener.onWarStart(seed); 120 | currentWar.loadWarriorGroups(warriorGroups); 121 | 122 | // go go go! 123 | int round = 0; 124 | while (round < MAX_ROUND) { 125 | competitionEventListener.onRound(round); 126 | 127 | competitionEventListener.onEndRound(); 128 | 129 | // apply speed limits 130 | if (speed != MAXIMUM_SPEED) { 131 | // note: if speed is 1 (meaning game is paused), this will 132 | // always happen 133 | if (round % speed == 0) { 134 | Thread.sleep(DELAY_UNIT); 135 | } 136 | 137 | if (speed == 1) { // paused 138 | continue; 139 | } 140 | } 141 | 142 | //pause 143 | while (currentWar.isPaused()) Thread.sleep(DELAY_UNIT); 144 | 145 | //Single step run - stop next time 146 | if (currentWar.isSingleRound()) 147 | currentWar.pause(); 148 | 149 | if (currentWar.isOver()) { 150 | break; 151 | } 152 | 153 | currentWar.nextRound(round); 154 | 155 | ++round; 156 | } 157 | competitionEventListener.onRound(round); 158 | 159 | int numAlive = currentWar.getNumRemainingWarriors(); 160 | String names = currentWar.getRemainingWarriorNames(); 161 | 162 | if (numAlive == 1) { // we have a single winner! 163 | competitionEventListener.onWarEnd(CompetitionEventListener.SINGLE_WINNER, names); 164 | } else if (round == MAX_ROUND) { // maximum round reached 165 | competitionEventListener.onWarEnd(CompetitionEventListener.MAX_ROUND_REACHED, names); 166 | } else { // user abort 167 | competitionEventListener.onWarEnd(CompetitionEventListener.ABORTED, names); 168 | } 169 | currentWar.updateScores(warriorRepository); 170 | currentWar = null; 171 | } 172 | 173 | public void runWarInParallel(WarriorGroup[] warriorGroups, long seed, int id) throws Exception { 174 | War war = new War(memoryEventListener, competitionEventListener, false, options); 175 | war.setSeed(seed); 176 | boolean selectedAsCurrent = false; 177 | 178 | // Set current war as a 179 | synchronized (this) { 180 | if (this.currentWar == null) { 181 | this.currentWar = war; 182 | selectedAsCurrent = true; 183 | } 184 | } 185 | 186 | competitionEventListener.onWarStart(seed); 187 | war.loadWarriorGroups(warriorGroups); 188 | 189 | int round = 0; 190 | while (round < MAX_ROUND) { 191 | competitionEventListener.onRound(round); 192 | competitionEventListener.onEndRound(); 193 | 194 | if (selectedAsCurrent && speed != MAXIMUM_SPEED) { 195 | if (round % speed == 0) { 196 | Thread.sleep(DELAY_UNIT); 197 | } 198 | } 199 | 200 | if (war.isOver()) { 201 | break; 202 | } 203 | 204 | war.nextRound(round); 205 | ++round; 206 | } 207 | 208 | competitionEventListener.onRound(round); 209 | 210 | int numAlive = war.getNumRemainingWarriors(); 211 | String names = war.getRemainingWarriorNames(); 212 | 213 | if (numAlive == 1) { // we have a single winner! 214 | competitionEventListener.onWarEnd(CompetitionEventListener.SINGLE_WINNER, names); 215 | } else if (round == MAX_ROUND) { // maximum round reached 216 | competitionEventListener.onWarEnd(CompetitionEventListener.MAX_ROUND_REACHED, names); 217 | } else { // user abort 218 | competitionEventListener.onWarEnd(CompetitionEventListener.ABORTED, names); 219 | } 220 | 221 | if (selectedAsCurrent) { 222 | synchronized (this) { 223 | this.currentWar = null; 224 | } 225 | } 226 | 227 | synchronized (warriorRepository) { 228 | war.updateScores(warriorRepository); 229 | } 230 | } 231 | 232 | public int getCurrentWarrior() { 233 | if (currentWar != null) { 234 | return currentWar.getCurrentWarrior(); 235 | } else { 236 | return -1; 237 | } 238 | } 239 | 240 | public void addCompetitionEventListener(CompetitionEventListener lis) { 241 | competitionEventCaster.add(lis); 242 | } 243 | public void removeCompetitionEventListener(CompetitionEventListener lis) { 244 | competitionEventCaster.remove(lis); 245 | } 246 | 247 | public void addMemoryEventLister(MemoryEventListener lis) { 248 | memoryEventCaster.add(lis); 249 | } 250 | 251 | public void removeMemoryEventLister(MemoryEventListener lis) { 252 | memoryEventCaster.remove(lis); 253 | } 254 | 255 | public WarriorRepository getWarriorRepository() { 256 | return warriorRepository; 257 | } 258 | 259 | /** 260 | * Set the speed of the competition, wither MAX_SPEED or a positive integer 261 | * when 1 is the slowest speed 262 | * @param speed 263 | */ 264 | public void setSpeed(int speed) { 265 | this.speed = speed; 266 | } 267 | 268 | public int getSpeed() { 269 | return speed; 270 | } 271 | 272 | public void setAbort(boolean abort) { 273 | this.abort = abort; 274 | 275 | if (abort && executorService != null) { 276 | executorService.shutdownNow(); 277 | executorService = null; 278 | } 279 | } 280 | 281 | 282 | public War getCurrentWar(){ 283 | return currentWar; 284 | } 285 | 286 | public void setSeed(long seed){ 287 | this.seed = seed; 288 | } 289 | 290 | public long getSeed(){ 291 | return seed; 292 | } 293 | 294 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/CompetitionEventListener.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import java.util.EventListener; 4 | 5 | /** 6 | * Interface for War events' listeners (implemented by the UI). 7 | * 8 | * @author DL 9 | */ 10 | public interface CompetitionEventListener extends EventListener { 11 | 12 | /** 13 | * Called when a new War is started. 14 | * @param seed Seed for the war's RNG features. 15 | */ 16 | void onWarStart(long seed); 17 | 18 | /** Possible reasons for a war session to end. */ 19 | public static final int SINGLE_WINNER = 0; 20 | public static final int MAX_ROUND_REACHED = 1; 21 | public static final int ABORTED = 2; 22 | 23 | /** 24 | * Called when a War ends. 25 | * @param reason One of the above reasons. 26 | * @param winners Winning warrior(s) name(s). 27 | */ 28 | void onWarEnd(int reason, String winners); 29 | 30 | /** 31 | * Called when a new round is started. 32 | * @param round 0-based round number. 33 | */ 34 | void onRound(int round); 35 | 36 | /** 37 | * Called when a new warrior enters the arena. 38 | * @param warriorName Warrior's name. 39 | */ 40 | void onWarriorBirth(String warriorName); 41 | 42 | /** 43 | * Called when a warrior dies. 44 | * @param warriorName Warrior's name. 45 | * @param reason Reason for death. 46 | */ 47 | void onWarriorDeath(String warriorName, String reason); 48 | 49 | void onCompetitionStart(); 50 | 51 | void onCompetitionEnd(); 52 | 53 | void onEndRound(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/CompetitionIterator.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import org.apache.commons.math3.random.RandomDataGenerator; 4 | import org.apache.commons.math3.util.CombinatoricsUtils; 5 | 6 | import java.util.Iterator; 7 | 8 | public class CompetitionIterator implements Iterator { 9 | private final RandomDataGenerator rnd; 10 | private int[] counters; 11 | private int numItems; 12 | private final int groupSize; 13 | 14 | public CompetitionIterator(int numItems, int groupSize) { 15 | assert numItems >= groupSize; 16 | this.numItems = numItems; 17 | this.groupSize = groupSize; 18 | counters = new int[groupSize]; 19 | rnd = new RandomDataGenerator(); 20 | } 21 | 22 | /** 23 | * Returns the next group in the sequence 24 | */ 25 | public int[] next() 26 | { 27 | return rnd.nextPermutation(numItems, Math.min(groupSize, numItems)); 28 | } 29 | 30 | public boolean hasNext() { 31 | return counters[0] != -1; 32 | } 33 | 34 | public void remove() { 35 | } 36 | 37 | public long getNumberOfItems() { 38 | return CombinatoricsUtils.binomialCoefficient(numItems, counters.length); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/ScoreEventListener.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import java.util.EventListener; 4 | 5 | /** 6 | * @author BS 7 | */ 8 | public interface ScoreEventListener extends EventListener { 9 | void scoreChanged(String name, float addedValue, int groupIndex, int subIndex); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/War.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | import il.co.codeguru.corewars8086.cpu.CpuException; 5 | import il.co.codeguru.corewars8086.memory.MemoryEventListener; 6 | import il.co.codeguru.corewars8086.memory.MemoryException; 7 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 8 | import il.co.codeguru.corewars8086.memory.RealModeMemoryImpl; 9 | import il.co.codeguru.corewars8086.utils.Unsigned; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Random; 14 | 15 | 16 | /** 17 | * Implements the main "war-logic". 18 | * 19 | * @author DL 20 | */ 21 | public class War { 22 | /** Arena's code segment */ 23 | public final static short ARENA_SEGMENT = 0x1000; 24 | /** Arena's size in bytes (= size of a single segment) */ 25 | public final static int ARENA_SIZE = 26 | RealModeAddress.PARAGRAPHS_IN_SEGMENT * RealModeAddress.PARAGRAPH_SIZE; 27 | /** Warrior's private stack size */ 28 | private final static short STACK_SIZE = 2*1024; 29 | /** Group-shared private memory size */ 30 | private final static short GROUP_SHARED_MEMORY_SIZE = 1024; 31 | /** Arena is filled with this byte */ 32 | private final static byte ARENA_BYTE = (byte)0xCC; 33 | /** Maximum number of warriors in a fight */ 34 | private final static int MAX_WARRIORS = 20; 35 | /** Maximum attempts to load a warrior to the Arena */ 36 | private final static int MAX_LOADING_TRIES = 100; 37 | /** Minimum initial space (in bytes) between loaded warriors */ 38 | private final static int MIN_GAP = 1024; 39 | 40 | /** Warriors in the fight */ 41 | private Warrior[] m_warriors; 42 | /** Number of loaded warriors */ 43 | private int m_numWarriors; 44 | /** Number of warriors still alive */ 45 | private int m_numWarriorsAlive; 46 | /** 47 | * Addresses equal or larger than this are still unused. 48 | * An address can be 'used' either by the Arena, or by the private stacks. 49 | */ 50 | private int m_nextFreeAddress; 51 | /** The 'physical' memory core */ 52 | private RealModeMemoryImpl m_core; 53 | 54 | /** The number of the current warrior */ 55 | private int m_currentWarrior; 56 | 57 | /** The listener for war events */ 58 | private CompetitionEventListener m_warListener; 59 | 60 | private final Options options; 61 | 62 | /** 63 | * Constructor. 64 | * Fills the Arena with its initial data. 65 | */ 66 | public War(MemoryEventListener memoryListener, 67 | CompetitionEventListener warListener, 68 | boolean startPaused, 69 | Options options) { 70 | this.options = options; 71 | isPaused = startPaused; 72 | m_warListener = warListener; 73 | m_warriors = new Warrior[MAX_WARRIORS]; 74 | m_numWarriors = 0; 75 | m_numWarriorsAlive = 0; 76 | m_core = new RealModeMemoryImpl(); 77 | m_nextFreeAddress = RealModeAddress.PARAGRAPH_SIZE * 78 | (ARENA_SEGMENT + RealModeAddress.PARAGRAPHS_IN_SEGMENT); 79 | 80 | // initialize arena 81 | for (int offset = 0; offset < ARENA_SIZE; ++offset) { 82 | RealModeAddress tmp = new RealModeAddress(ARENA_SEGMENT, (short)offset); 83 | m_core.writeByte(tmp, ARENA_BYTE); 84 | } 85 | 86 | isSingleRound = false; 87 | 88 | // set the memory listener (we only do this now, to skip initialization) 89 | m_core.setListener(memoryListener); 90 | } 91 | 92 | /** 93 | * Loads the given warrior groups to the Arena. 94 | * @param warriorGroups The warrior groups to load. 95 | * @throws Exception 96 | */ 97 | public void loadWarriorGroups(WarriorGroup[] warriorGroups) throws Exception { 98 | m_currentWarrior = 0; 99 | ArrayList groupsLeftToLoad = new ArrayList(); 100 | for (int i = 0; i < warriorGroups.length; ++i) 101 | groupsLeftToLoad.add(warriorGroups[i]); 102 | 103 | while (groupsLeftToLoad.size() > 0) 104 | { 105 | int randomInt = rand.nextInt(groupsLeftToLoad.size()); 106 | loadWarriorGroup(groupsLeftToLoad.get(randomInt)); 107 | groupsLeftToLoad.remove(randomInt); 108 | } 109 | } 110 | 111 | /** 112 | * Runs a single round of the war (every living warrior does his turn). 113 | * @param round The current round number. 114 | */ 115 | public void nextRound(int round) { 116 | for (int i = 0; i < m_numWarriors; ++i) { 117 | Warrior warrior = m_warriors[i]; 118 | m_currentWarrior = i; 119 | if (warrior.isAlive()) { 120 | try { 121 | // run first opcode 122 | warrior.nextOpcode(); 123 | 124 | if (warrior.isZombie()) { 125 | // Zombie H runs at double speed 126 | int remainingRounds = 127 | (options.zombieSpeed * ((warrior.getType() == WarriorType.ZOMBIE_H) ? 2 : 1)) - 1; 128 | 129 | for (int j = 0; j < remainingRounds; j++) { 130 | warrior.nextOpcode(); 131 | } 132 | } else { // run one extra opcode, if warrior deserves it :) 133 | updateWarriorEnergy(warrior, round); 134 | if (shouldRunExtraOpcode(warrior)) { 135 | warrior.nextOpcode(); 136 | } 137 | } 138 | } catch (CpuException e) { 139 | m_warListener.onWarriorDeath(warrior.getName(), "CPU exception"); 140 | warrior.kill(); 141 | --m_numWarriorsAlive; 142 | } catch (MemoryException e) { 143 | m_warListener.onWarriorDeath(warrior.getName(), "memory exception"); 144 | warrior.kill(); 145 | --m_numWarriorsAlive; 146 | } 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * @return whether or not the War is over. 153 | */ 154 | public boolean isOver() { 155 | return (m_numWarriorsAlive < 2); 156 | } 157 | 158 | /** 159 | * Decrements the warrior's Energy value, if the current round is 160 | * a multiple of DECELERATION_ROUNDS. 161 | * 162 | * @param warrior The warrior. 163 | * @param round Current round number. 164 | */ 165 | private void updateWarriorEnergy(Warrior warrior, int round) { 166 | if ((round % DECELERATION_ROUNDS) == 0) { 167 | int energy = Unsigned.unsignedShort(warrior.getEnergy()); 168 | 169 | if (energy > 0 ) { 170 | warrior.setEnergy((short)(energy-1)); 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * Determines whether or not a given warrior deserves an extra opcode, 177 | * by calculating the warrior's current speed (using its current Energy 178 | * value), and comparing it against a random value. 179 | * 180 | * We use a random-based algorithm (as opposed to a deterministic one) for 181 | * the following reasons: 182 | * a) simple implementation - there is no need to keep record of past 183 | * decisions as our algorithm is stateless. 184 | * b) we want the warrior's speed to vary between x1.0 to x2.0, and this 185 | * solves the issue of determining what to do if the current speed is x1.7 :) 186 | * 187 | * @param warrior The warrior. 188 | * @return true if the warrior deserves an extra opcode, otherwise 189 | * returns false. 190 | */ 191 | private boolean shouldRunExtraOpcode(Warrior warrior) { 192 | int energy = Unsigned.unsignedShort(warrior.getEnergy()); 193 | int speed = calculateWarriorSpeed(energy); 194 | 195 | return (rand.nextInt(MAX_SPEED) < speed); 196 | } 197 | 198 | /** Maximum possible Warrior speed. */ 199 | private final int MAX_SPEED = 16; // when Energy = 0xFFFF 200 | /** Warrior's Energy is decremented every so often rounds. */ 201 | private final int DECELERATION_ROUNDS = 5; 202 | 203 | /** 204 | * Returns the warrior's current speed, using the following formula: 205 | * Speed := Min(MAX_SPEED, 1+Log2(Energy)) 206 | * 207 | * This formula forces the warrior to put more and more effort in order to 208 | * increase its speed, i.e. non-linear effort. 209 | * 210 | * @param energy The warrior's Energy value. 211 | * @return the warrior's current speed, 212 | */ 213 | private int calculateWarriorSpeed(int energy) { 214 | if (energy == 0) { 215 | return 0; 216 | } else { 217 | return Math.min(MAX_SPEED, 1 + (int)(Math.log(energy) / Math.log(2))); 218 | } 219 | } 220 | 221 | private void loadWarriorGroup(WarriorGroup warriorGroup) throws Exception { 222 | List warriors = warriorGroup.getWarriors(); 223 | 224 | RealModeAddress groupSharedMemory = 225 | allocateCoreMemory(GROUP_SHARED_MEMORY_SIZE); 226 | 227 | for (int i = 0; i < warriors.size(); ++i) { 228 | 229 | WarriorData warrior = warriors.get(i); 230 | 231 | String warriorName = warrior.getName(); 232 | byte[] warriorData = warrior.getCode(); 233 | 234 | short loadOffset = getLoadOffset(warriorData.length); 235 | 236 | RealModeAddress loadAddress = 237 | new RealModeAddress(ARENA_SEGMENT, loadOffset); 238 | RealModeAddress stackMemory = allocateCoreMemory(STACK_SIZE); 239 | RealModeAddress initialStack = 240 | new RealModeAddress(stackMemory.getSegment(), STACK_SIZE); 241 | 242 | m_warriors[m_numWarriors++] = new Warrior( 243 | warriorName, 244 | warriorData.length, 245 | m_core, 246 | loadAddress, 247 | initialStack, 248 | groupSharedMemory, 249 | GROUP_SHARED_MEMORY_SIZE, 250 | warrior.getType() 251 | ); 252 | 253 | // load warrior to arena 254 | for (int offset = 0; offset < warriorData.length; ++offset) { 255 | RealModeAddress tmp = new RealModeAddress(ARENA_SEGMENT, (short)(loadOffset + offset)); 256 | m_core.writeByte(tmp, warriorData[offset]); 257 | } 258 | ++m_numWarriorsAlive; 259 | ++m_currentWarrior; 260 | 261 | // notify listener 262 | m_warListener.onWarriorBirth(warriorName); 263 | } 264 | } 265 | 266 | /** 267 | * Virtually allocates core memory of a given size, by advancing the 268 | * next-free-memory pointer (m_nextFreeAddress). 269 | * 270 | * @param size Required memory size, must be a multiple of 271 | * RealModeAddress.PARAGRAPH_SIZE 272 | * @return Pointer to the beginning of the allocated memory block. 273 | */ 274 | private RealModeAddress allocateCoreMemory(short size) throws Exception { 275 | if ((size % RealModeAddress.PARAGRAPH_SIZE) != 0) { 276 | throw new Exception(); 277 | } 278 | 279 | RealModeAddress allocatedMemory = 280 | new RealModeAddress(m_nextFreeAddress); 281 | 282 | m_nextFreeAddress += size; 283 | 284 | return allocatedMemory; 285 | } 286 | 287 | /** 288 | * Returns a suitable random address to which a warrior with a given code 289 | * size can be loaded. 290 | * 291 | * A suitable address is- 292 | * 1. far enough from the Arena's boundaries. 293 | * 2. far enough from other loaded warriors. 294 | * 295 | * @param warriorSize Code size of the loaded warrior. 296 | * @return offset within the Arena to which the warrior can be loaded. 297 | * @throws Exception if no suitable address could be found. 298 | */ 299 | private short getLoadOffset(int warriorSize) throws Exception { 300 | int loadAddress = 0; 301 | boolean found = false; 302 | int numTries = 0; 303 | 304 | while ((!found) && (numTries < MAX_LOADING_TRIES)) { 305 | ++numTries; 306 | 307 | loadAddress = rand.nextInt(ARENA_SIZE); 308 | found = true; 309 | 310 | if (loadAddress < MIN_GAP) { 311 | found = false; 312 | } 313 | 314 | if (loadAddress+warriorSize > ARENA_SIZE-MIN_GAP) { 315 | found = false; 316 | } 317 | 318 | for (int i = 0; i < m_numWarriors; ++i) { 319 | int otherLoadAddress = 320 | Unsigned.unsignedShort(m_warriors[i].getLoadOffset()); 321 | int otherSize = m_warriors[i].getCodeSize(); 322 | 323 | int otherStart = otherLoadAddress-MIN_GAP; 324 | int otherEnd = otherLoadAddress+otherSize+MIN_GAP; 325 | 326 | if ((loadAddress+warriorSize >= otherStart) && (loadAddress < otherEnd)) { 327 | found = false; 328 | } 329 | } 330 | } 331 | 332 | if (!found) { 333 | throw new Exception(); 334 | } 335 | 336 | return (short)loadAddress; 337 | } 338 | 339 | /** 340 | * @return Returns the currentWarrior. 341 | */ 342 | public int getCurrentWarrior() { 343 | return m_currentWarrior; 344 | } 345 | 346 | /** @return the warrior with the given index */ 347 | public Warrior getWarrior(int index) { 348 | return m_warriors[index]; 349 | } 350 | 351 | /** @return the numebr of warriors fighting in this match. */ 352 | public int getNumWarriors() { 353 | return m_numWarriors; 354 | } 355 | 356 | /** @return the number of warriors still alive. */ 357 | public int getNumRemainingWarriors() { 358 | return m_numWarriorsAlive; 359 | } 360 | 361 | /** @return a comma-seperated list of all warriors still alive. */ 362 | public String getRemainingWarriorNames() { 363 | String names = ""; 364 | for (int i = 0; i < m_numWarriors; ++i) { 365 | Warrior warrior = m_warriors[i]; 366 | if (warrior.isAlive()) { 367 | if (names == "") { 368 | names = warrior.getName(); 369 | } else { 370 | names = names + ", " + warrior.getName(); 371 | } 372 | } 373 | } 374 | return names; 375 | } 376 | 377 | /** 378 | * Updates the scores in a given score-board. 379 | */ 380 | public void updateScores(WarriorRepository repository) { 381 | float score = (float)1.0 / m_numWarriorsAlive; 382 | for (int i = 0; i < m_numWarriors; ++i) { 383 | Warrior warrior = m_warriors[i]; 384 | if (warrior.isAlive()) { 385 | repository.addScore(warrior.getName(), score); 386 | } /*else { 387 | scoreBoard.addScore(warrior.getName(), 0); 388 | }*/ 389 | } 390 | } 391 | 392 | private Random rand = new Random(); 393 | 394 | private boolean isSingleRound; 395 | private boolean isPaused; 396 | 397 | public void setSeed(long seed){ 398 | rand.setSeed(seed); 399 | } 400 | 401 | public void pause(){ 402 | isPaused = true; 403 | } 404 | 405 | public boolean isPaused(){ 406 | return isPaused; 407 | } 408 | 409 | public void resume(){ 410 | isPaused = false; 411 | isSingleRound = false; 412 | } 413 | 414 | public void runSingleRound(){ 415 | this.resume(); 416 | isSingleRound = true; 417 | } 418 | 419 | public boolean isSingleRound(){ 420 | return this.isSingleRound; 421 | } 422 | 423 | public RealModeMemoryImpl getMemory(){ 424 | return m_core; 425 | } 426 | 427 | 428 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/Warrior.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import il.co.codeguru.corewars8086.cpu.Cpu; 4 | import il.co.codeguru.corewars8086.cpu.CpuException; 5 | import il.co.codeguru.corewars8086.cpu.CpuState; 6 | import il.co.codeguru.corewars8086.memory.MemoryException; 7 | import il.co.codeguru.corewars8086.memory.RealModeAddress; 8 | import il.co.codeguru.corewars8086.memory.RealModeMemory; 9 | import il.co.codeguru.corewars8086.memory.RealModeMemoryRegion; 10 | import il.co.codeguru.corewars8086.memory.RestrictedAccessRealModeMemory; 11 | 12 | 13 | /** 14 | * A single CoreWars warrior. 15 | * 16 | * @author DL 17 | */ 18 | public class Warrior { 19 | 20 | /** 21 | * Constructor. 22 | * 23 | * @param name Warrior's name. 24 | * @param codeSize Warrior's code size. 25 | * @param core Real mode memory used as core. 26 | * @param loadAddress Warrior's load address in the core (initial CS:IP). 27 | * @param initialStack Warrior's private stack in the core (initial SS:SP). 28 | * @param groupSharedMemory Warrior group's shared memroy address (initial ES). 29 | * @param groupSharedMemorySize Warrior group's shared memory size. 30 | */ 31 | public Warrior( 32 | String name, 33 | int codeSize, 34 | RealModeMemory core, 35 | RealModeAddress loadAddress, 36 | RealModeAddress initialStack, 37 | RealModeAddress groupSharedMemory, 38 | short groupSharedMemorySize, 39 | WarriorType type) { 40 | 41 | this.type = type; 42 | m_name = name; 43 | m_codeSize = codeSize; 44 | m_loadAddress = loadAddress; 45 | 46 | m_state = new CpuState(); 47 | initializeCpuState(loadAddress, initialStack, groupSharedMemory); 48 | 49 | // initialize read-access regions 50 | RealModeAddress lowestStackAddress = 51 | new RealModeAddress(initialStack.getSegment(), (short)0); 52 | RealModeAddress lowestCoreAddress = 53 | new RealModeAddress(loadAddress.getSegment(), (short)0); 54 | RealModeAddress highestCoreAddress = 55 | new RealModeAddress(loadAddress.getSegment(), (short)-1); 56 | RealModeAddress highestGroupSharedMemoryAddress = 57 | new RealModeAddress(groupSharedMemory.getSegment(), 58 | (short)(groupSharedMemorySize-1)); 59 | 60 | RealModeMemoryRegion[] readAccessRegions = 61 | new RealModeMemoryRegion[] { 62 | new RealModeMemoryRegion(lowestStackAddress, initialStack), 63 | new RealModeMemoryRegion(lowestCoreAddress, highestCoreAddress), 64 | new RealModeMemoryRegion(groupSharedMemory, highestGroupSharedMemoryAddress) 65 | }; 66 | 67 | // initialize write-access regions 68 | RealModeMemoryRegion[] writeAccessRegions = 69 | new RealModeMemoryRegion[] { 70 | new RealModeMemoryRegion(lowestStackAddress, initialStack), 71 | new RealModeMemoryRegion(lowestCoreAddress, highestCoreAddress), 72 | new RealModeMemoryRegion(groupSharedMemory, highestGroupSharedMemoryAddress) 73 | }; 74 | 75 | // initialize execute-access regions 76 | RealModeMemoryRegion[] executeAccessRegions = 77 | new RealModeMemoryRegion[] { 78 | new RealModeMemoryRegion(lowestCoreAddress, highestCoreAddress) 79 | }; 80 | 81 | m_memory = new RestrictedAccessRealModeMemory( 82 | core, readAccessRegions, writeAccessRegions, executeAccessRegions); 83 | 84 | m_cpu = new Cpu(m_state, m_memory); 85 | 86 | m_isAlive = true; 87 | } 88 | 89 | /** 90 | * @return whether or not the warrior is still alive. 91 | */ 92 | public boolean isAlive() { 93 | return m_isAlive; 94 | } 95 | 96 | /** 97 | * Kills the warrior. 98 | */ 99 | public void kill() { 100 | m_isAlive = false; 101 | } 102 | 103 | public boolean isZombie() { 104 | return type == WarriorType.ZOMBIE || type == WarriorType.ZOMBIE_H; 105 | } 106 | 107 | public WarriorType getType() { 108 | return type; 109 | } 110 | 111 | /** 112 | * @return the warrior's name. 113 | */ 114 | public String getName() { 115 | return m_name; 116 | } 117 | 118 | /** 119 | * @return the warrior's load offset. 120 | */ 121 | public short getLoadOffset() { 122 | return m_loadAddress.getOffset(); 123 | } 124 | 125 | /** 126 | * @return the warrior's initial code size. 127 | */ 128 | public int getCodeSize() { 129 | return m_codeSize; 130 | } 131 | 132 | /** 133 | * Accessors for the warrior's Energy value (used to calculate 134 | * the warrior's speed). 135 | */ 136 | public short getEnergy() { 137 | return m_state.getEnergy(); 138 | } 139 | public void setEnergy(short value) { 140 | m_state.setEnergy(value); 141 | } 142 | 143 | /** 144 | * Performs the warrior's next turn (= next opcode). 145 | * @throws CpuException on any CPU error. 146 | * @throws MemoryException on any Memory error. 147 | */ 148 | public void nextOpcode() throws CpuException, MemoryException { 149 | m_cpu.nextOpcode(); 150 | } 151 | 152 | /** 153 | * Initializes the Cpu registers & flags: 154 | * CS,DS - set to the core's segment. 155 | * ES - set to the group's shared memory segment. 156 | * AX,IP - set to the load address. 157 | * SS - set to the private stack's segment. 158 | * SP - set to the private stack's offset. 159 | * BX,CX,DX,SI,DI,BP, flags - set to zero. 160 | * 161 | * @param loadAddress Warrior's load address in the core. 162 | * @param initialStack Warrior's private stack (initial SS:SP). 163 | * @param groupSharedMemory The warrior's group shared memory. 164 | */ 165 | private void initializeCpuState( 166 | RealModeAddress loadAddress, RealModeAddress initialStack, 167 | RealModeAddress groupSharedMemory) { 168 | 169 | // initialize registers 170 | m_state.setAX(loadAddress.getOffset()); 171 | m_state.setBX((short)0); 172 | m_state.setCX((short)0); 173 | m_state.setDX((short)0); 174 | 175 | m_state.setDS(loadAddress.getSegment()); 176 | m_state.setES(groupSharedMemory.getSegment()); 177 | m_state.setSI((short)0); 178 | m_state.setDI((short)0); 179 | 180 | m_state.setSS(initialStack.getSegment()); 181 | m_state.setBP((short)0); 182 | m_state.setSP(initialStack.getOffset()); 183 | 184 | m_state.setCS(loadAddress.getSegment()); 185 | m_state.setIP(loadAddress.getOffset()); 186 | m_state.setFlags((short)0); 187 | 188 | // initialize Energy 189 | m_state.setEnergy((short)0); 190 | 191 | // initialize bombs 192 | m_state.setBomb1Count((byte)2); 193 | m_state.setBomb2Count((byte)1); 194 | } 195 | 196 | public CpuState getCpuState(){ 197 | return m_state; 198 | } 199 | 200 | /** Warrior's name */ 201 | private final String m_name; 202 | /** Warrior's initial code size */ 203 | private final int m_codeSize; 204 | /** Warrior's initial load address */ 205 | private final RealModeAddress m_loadAddress; 206 | /** Current state of registers & flags */ 207 | private CpuState m_state; 208 | /** Applies restricted access logic on top of the actual core memory */ 209 | private RestrictedAccessRealModeMemory m_memory; 210 | /** CPU instance */ 211 | private Cpu m_cpu; 212 | /** Whether or not the warrior is still alive */ 213 | private boolean m_isAlive; 214 | 215 | private final WarriorType type; 216 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/WarriorData.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | /** 4 | * Holds a single warrior's name & code. 5 | * 6 | * @author DL 7 | */ 8 | public class WarriorData { 9 | 10 | /** 11 | * Constructor. 12 | * @param name Warrior's name. 13 | * @param code Warrior's code. 14 | * @param type Warrior's type ( 15 | */ 16 | public WarriorData(String name, byte[] code, WarriorType type) { 17 | m_name = name; 18 | m_code = code; 19 | this.type = type; 20 | } 21 | 22 | /** @return the warrior's name. */ 23 | public String getName() { 24 | return m_name; 25 | } 26 | 27 | /** @return the warrior's code. */ 28 | public byte[] getCode() { 29 | return m_code; 30 | } 31 | 32 | public WarriorType getType() { 33 | return type; 34 | } 35 | 36 | /** Holds warrior's name */ 37 | private final String m_name; 38 | 39 | /** Holds warrior's code */ 40 | private final byte[] m_code; 41 | 42 | private final WarriorType type; 43 | 44 | 45 | @Override 46 | public String toString() { 47 | return m_name; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/WarriorFilenameFilter.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import java.io.File; 4 | import java.io.FilenameFilter; 5 | 6 | /** 7 | * Filters warrior files according to extension (comparison is case insensitive ). 8 | * 9 | * @author DL 10 | */ 11 | public class WarriorFilenameFilter implements FilenameFilter { 12 | 13 | WarriorFilenameFilter(String extension) { 14 | m_extension = extension; 15 | } 16 | 17 | public boolean accept(File arg0, String arg1) { 18 | String filename = arg1.toUpperCase(); 19 | return filename.endsWith(m_extension); 20 | } 21 | 22 | private final String m_extension; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/WarriorGroup.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class WarriorGroup { 7 | private String name; 8 | private ArrayList warriorData; 9 | private List scores; 10 | private float groupScore; 11 | 12 | public WarriorGroup(String name) { 13 | this.name = name; 14 | warriorData = new ArrayList<>(); 15 | scores = new ArrayList<>(); 16 | } 17 | 18 | public void addWarrior(WarriorData data) { 19 | warriorData.add(data); 20 | scores.add(0f); 21 | } 22 | 23 | public List getWarriors() { 24 | return warriorData; 25 | } 26 | 27 | public List getScores() { 28 | return scores; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public float getGroupScore() { 36 | return groupScore; 37 | } 38 | 39 | public int addScoreToWarrior(String name, float value) { 40 | // find this warrior 41 | int i; 42 | for (i = 0; i < warriorData.size(); i++) { 43 | if (warriorData.get(i).getName().equals(name)) { 44 | scores.set(i, scores.get(i) + value); 45 | break; 46 | } 47 | } 48 | groupScore += value; 49 | return i; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/WarriorRepository.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | import il.co.codeguru.corewars8086.cli.Options; 4 | import il.co.codeguru.corewars8086.utils.EventMulticaster; 5 | 6 | import java.io.*; 7 | import java.util.*; 8 | 9 | import javax.swing.JOptionPane; 10 | 11 | 12 | public class WarriorRepository { 13 | 14 | /** 15 | * Maximum initial code size of a single warrior 16 | */ 17 | private final static int MAX_WARRIOR_SIZE = 512; 18 | 19 | private List warriorGroups; 20 | private WarriorGroup zombieGroup; 21 | private Map warriorNameToGroup; 22 | 23 | private EventMulticaster scoreEventsCaster; 24 | private ScoreEventListener scoreListener; 25 | 26 | private final Options options; 27 | 28 | public WarriorRepository(Options options) throws IOException { 29 | this(true, options); 30 | } 31 | public WarriorRepository(boolean shouldReadWarriorsFile, Options options) throws IOException { 32 | this.options = options; 33 | warriorNameToGroup = new HashMap<>(); 34 | warriorGroups = new ArrayList<>(); 35 | if (shouldReadWarriorsFile) 36 | readWarriorFiles(); 37 | 38 | scoreEventsCaster = new EventMulticaster(ScoreEventListener.class); 39 | scoreListener = (ScoreEventListener) scoreEventsCaster.getProxy(); 40 | } 41 | 42 | public void addScoreEventListener(ScoreEventListener lis) { 43 | scoreEventsCaster.add(lis); 44 | } 45 | 46 | public void addScore(String name, float value) { 47 | Integer groupIndex = warriorNameToGroup.get(name); 48 | if (groupIndex == null) {// zombies 49 | return; 50 | } 51 | WarriorGroup group = warriorGroups.get(groupIndex); 52 | int subIndex = group.addScoreToWarrior(name, value); 53 | scoreListener.scoreChanged(name, value, groupIndex, subIndex); 54 | } 55 | 56 | public int getNumberOfGroups() { 57 | return warriorGroups.size(); 58 | } 59 | 60 | public String[] getGroupNames() { 61 | List names = new ArrayList<>(); 62 | for (WarriorGroup group : warriorGroups) { 63 | names.add(group.getName()); 64 | } 65 | return names.toArray(new String[0]); 66 | } 67 | 68 | /** 69 | * Reads all warrior data files from the warriors' directory. 70 | * 71 | * @throws IOException 72 | */ 73 | private void readWarriorFiles() throws IOException { 74 | readWarriorsFileFromPath(options.warriorsDir); 75 | readZombiesFiles(); 76 | } 77 | 78 | private void readZombiesFiles() throws IOException { 79 | readZombiesFileFromPath(options.zombiesDir); 80 | } 81 | 82 | public void readZombiesFileFromPath(String path) throws IOException { 83 | File zombieDirectory = new File(path); 84 | File[] zombieFiles = zombieDirectory.listFiles(); 85 | if (zombieFiles == null) { 86 | // no zombies! 87 | return; 88 | } 89 | zombieGroup = new WarriorGroup("ZoMbIeS"); 90 | for (File file : zombieFiles) { 91 | if (file.isDirectory()) { 92 | continue; 93 | } 94 | 95 | WarriorData data = readWarriorFile(file, WarriorType.ZOMBIE); 96 | zombieGroup.addWarrior(data); 97 | } 98 | } 99 | 100 | public void readWarriorsFileFromPath(String path) throws IOException { 101 | File warriorsDirectory = new File(path); 102 | 103 | fixFiles(warriorsDirectory); 104 | 105 | File[] warriorFiles = warriorsDirectory.listFiles(); 106 | if (warriorFiles == null) { 107 | JOptionPane.showMessageDialog(null, 108 | "Error - survivors directory (\"" + path + "\") not found"); 109 | System.exit(1); 110 | } 111 | 112 | WarriorGroup currentGroup = null; 113 | // sort by filename 114 | Arrays.sort(warriorFiles, (o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())); 115 | 116 | for (File file : warriorFiles) { 117 | if (file.isDirectory()) { 118 | continue; 119 | } 120 | 121 | String name = file.getName(); 122 | 123 | if (name.endsWith("1")) { 124 | // start a new group! 125 | currentGroup = new WarriorGroup(name.substring(0, name.length() - 1)); 126 | currentGroup.addWarrior(readWarriorFile(file, WarriorType.SURVIVOR_1)); 127 | warriorNameToGroup.put(name, warriorGroups.size()); 128 | } else if (name.endsWith("2")) { 129 | currentGroup.addWarrior(readWarriorFile(file, WarriorType.SURVIVOR_2)); 130 | warriorNameToGroup.put(name, warriorGroups.size()); 131 | warriorGroups.add(currentGroup); 132 | currentGroup = null; 133 | } else { 134 | currentGroup = new WarriorGroup(name); 135 | currentGroup.addWarrior(readWarriorFile(file, WarriorType.SURVIVOR)); 136 | warriorNameToGroup.put(name, warriorGroups.size()); 137 | warriorGroups.add(currentGroup); 138 | currentGroup = null; 139 | } 140 | } 141 | } 142 | 143 | private void fixFiles(File warriorsDirectory) { 144 | if (!warriorsDirectory.exists()) { 145 | throw new RuntimeException("Missing directory " + warriorsDirectory.getAbsolutePath()); 146 | } 147 | File[] files = warriorsDirectory.listFiles(); 148 | for (File file : files) { 149 | if (file.getName().endsWith(".bin")) { 150 | File renameTo = new File(file.getPath().replace(".bin", "")); 151 | renameTo.delete(); 152 | file.renameTo(renameTo); 153 | } 154 | } 155 | 156 | files = warriorsDirectory.listFiles(); 157 | for (File file : files) { 158 | if (file.getName().contains(".")) 159 | file.delete(); 160 | } 161 | } 162 | 163 | private static WarriorData readWarriorFile(File file, WarriorType type) throws IOException { 164 | String warriorName = file.getName(); 165 | 166 | int warriorSize = (int) file.length(); 167 | if (warriorSize > MAX_WARRIOR_SIZE) { 168 | warriorSize = MAX_WARRIOR_SIZE; 169 | } 170 | 171 | byte[] warriorData = new byte[warriorSize]; 172 | FileInputStream fis = new FileInputStream(file); 173 | int size = fis.read(warriorData); 174 | fis.close(); 175 | 176 | if (size != warriorSize) { 177 | throw new IOException(); 178 | } 179 | 180 | // Zombie H - runs at double speed 181 | if (type == WarriorType.ZOMBIE && warriorName.toLowerCase().endsWith("h")) { 182 | type = WarriorType.ZOMBIE_H; 183 | } 184 | 185 | return new WarriorData(warriorName, warriorData, type); 186 | } 187 | 188 | /** 189 | * @param groupIndices Required warrior groups indices. 190 | * @return the warrior groups corresponding to a given list of indices, and 191 | * the zombies group. 192 | */ 193 | public WarriorGroup[] createGroupList(int[] groupIndices) { 194 | ArrayList groupsList = new ArrayList(); 195 | 196 | // add requested warrior groups 197 | for (int groupIndex : groupIndices) { 198 | groupsList.add(warriorGroups.get(groupIndex)); 199 | } 200 | 201 | // add zombies (if exist) 202 | if (zombieGroup != null) { 203 | groupsList.add(zombieGroup); 204 | } 205 | 206 | WarriorGroup[] groups = new WarriorGroup[groupsList.size()]; 207 | groupsList.toArray(groups); 208 | return groups; 209 | } 210 | 211 | public void saveScoresToFile(String filename) { 212 | System.out.printf("Writing scores to file %s%n", filename); 213 | 214 | try (FileOutputStream fos = new FileOutputStream(filename)) { 215 | PrintStream ps = new PrintStream(fos); 216 | ps.print("Groups:\n"); 217 | for (WarriorGroup group : warriorGroups) { 218 | ps.print(group.getName() + "," + group.getGroupScore() + "\n"); 219 | } 220 | ps.print("\nWarriors:\n"); 221 | for (WarriorGroup group : warriorGroups) { 222 | List scores = group.getScores(); 223 | List data = group.getWarriors(); 224 | for (int i = 0; i < scores.size(); i++) { 225 | ps.print(data.get(i).getName() + "," + scores.get(i) + "\n"); 226 | } 227 | } 228 | } catch (IOException e) { 229 | e.printStackTrace(); 230 | } 231 | } 232 | 233 | public String getScores() { 234 | String str = ""; 235 | List sorted = new ArrayList<>(warriorGroups); 236 | sorted.sort((o1, o2) -> Float.compare(o2.getGroupScore(), o1.getGroupScore())); 237 | for (WarriorGroup group : sorted) { 238 | List scores = group.getScores(); 239 | List data = group.getWarriors(); 240 | str += group.getName() + "," + group.getGroupScore(); 241 | for (int i = 0; i < scores.size(); i++) { 242 | str += "," + data.get(i).getName() + "," + scores.get(i); 243 | } 244 | str += "\n"; 245 | } 246 | return str; 247 | } 248 | 249 | public List getWarriorGroups() { 250 | return warriorGroups; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/il/co/codeguru/corewars8086/war/WarriorType.java: -------------------------------------------------------------------------------- 1 | package il.co.codeguru.corewars8086.war; 2 | 3 | public enum WarriorType { 4 | SURVIVOR, SURVIVOR_1, SURVIVOR_2, ZOMBIE, ZOMBIE_H 5 | } 6 | --------------------------------------------------------------------------------