├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.core.resources.prefs
├── README.md
├── data
├── Supermario_BGM_Overworld.mp3
├── config.yml
├── img
│ ├── background_0000.gif
│ ├── block_0000.gif
│ ├── brick_0000.gif
│ ├── bw_mario_falling_0000.gif
│ ├── bw_mario_falling_0001.gif
│ ├── bw_mario_jumping_0000.gif
│ ├── bw_mario_running_0000.gif
│ ├── bw_mario_running_0001.gif
│ ├── bw_mario_running_0002.gif
│ ├── bw_mario_running_0003.gif
│ ├── bw_mario_standing_0000.gif
│ ├── gold_0000.gif
│ ├── gold_0001.gif
│ ├── gold_0002.gif
│ ├── gold_0003.gif
│ ├── ground_0000.gif
│ ├── ground_filler_0000.gif
│ ├── mario_falling_0000.gif
│ ├── mario_falling_0001.gif
│ ├── mario_jumping_0000.gif
│ ├── mario_running_0000.gif
│ ├── mario_running_0001.gif
│ ├── mario_running_0002.gif
│ ├── mario_running_0003.gif
│ ├── mario_standing_0000.gif
│ ├── princess_standing_0000.gif
│ └── question_0000.gif
└── map.txt
├── lib
├── core.jar
├── gluegen-rt-natives-linux-amd64.jar
├── gluegen-rt-natives-windows-amd64.jar
├── gluegen-rt.jar
├── gson-2.8.0.jar
├── jeromq-0.3.5.jar
├── jl1.0.1.jar
├── jogl-all-natives-linux-amd64.jar
├── jogl-all-natives-linux-armv6hf.jar
├── jogl-all-natives-windows-amd64.jar
├── jogl-all.jar
├── jsminim.jar
├── minim.jar
├── mp3spi1.9.5.jar
├── snakeyaml-1.17.jar
├── tritonus_aos.jar
└── tritonus_share.jar
├── pde
└── MarioMultiplay
│ ├── MarioMultiplay.pde
│ ├── code
│ ├── gson-2.8.0.jar
│ ├── jeromq-0.3.5.jar
│ └── snakeyaml-1.17.jar
│ └── data
│ ├── Supermario_BGM_Overworld.mp3
│ ├── config.yml
│ ├── img
│ ├── background_0000.gif
│ ├── block_0000.gif
│ ├── brick_0000.gif
│ ├── bw_mario_falling_0000.gif
│ ├── bw_mario_falling_0001.gif
│ ├── bw_mario_jumping_0000.gif
│ ├── bw_mario_running_0000.gif
│ ├── bw_mario_running_0001.gif
│ ├── bw_mario_running_0002.gif
│ ├── bw_mario_running_0003.gif
│ ├── bw_mario_standing_0000.gif
│ ├── gold_0000.gif
│ ├── gold_0001.gif
│ ├── gold_0002.gif
│ ├── gold_0003.gif
│ ├── ground_0000.gif
│ ├── ground_filler_0000.gif
│ ├── mario_falling_0000.gif
│ ├── mario_falling_0001.gif
│ ├── mario_jumping_0000.gif
│ ├── mario_running_0000.gif
│ ├── mario_running_0001.gif
│ ├── mario_running_0002.gif
│ ├── mario_running_0003.gif
│ ├── mario_standing_0000.gif
│ ├── princess_standing_0000.gif
│ └── question_0000.gif
│ └── map.txt
├── server
└── Program.cs
└── src
└── MarioMultiplay.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | MarioMultiplayer
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//src/MarioMultiplay.java=UTF-8
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Mario Multiplayer
2 | =================
3 |
4 | 
5 |
6 | Supermario multiplayer game, written in Java ([Processing](https://processing.org/) Java Mode) using [ZeroMQ](http://zeromq.org/) ([JeroMQ](https://github.com/zeromq/jeromq)).
7 |
8 | This repo also includes a server which is written in C# using ZeroMQ ([NetMQ](https://github.com/zeromq/netmq)).
9 |
10 | It's netcode is based on [State Synchronization](http://gafferongames.com/networked-physics/state-synchronization/).
11 |
12 | The client has [1500 lines of Java code in total](https://github.com/coolspeed/MarioMultiplayer/blob/master/src/MarioMultiplay.java), and the server [22 lines of C# code](https://github.com/coolspeed/MarioMultiplayer/blob/master/server/Program.cs).
13 |
14 | This project is developped as a team joining homework of [What! Studio](https://github.com/what-studio).
15 |
16 | ## Game rule
17 |
18 | The mario who first steps over the other wins.
19 |
20 | ## Design philosophy
21 |
22 | Described in my blog (in Korean):
23 |
24 | https://coolspeed.wordpress.com/2017/04/11/supermario_multiplayer_postmortem/
25 |
--------------------------------------------------------------------------------
/data/Supermario_BGM_Overworld.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/Supermario_BGM_Overworld.mp3
--------------------------------------------------------------------------------
/data/config.yml:
--------------------------------------------------------------------------------
1 | room_num: "123456"
2 | my_mario_num: 0
3 | my_frictional_force: 0.7
4 | server_host: 127.0.0.1
5 | show_packet_indicator: true
6 | packet_frugal: true
7 |
--------------------------------------------------------------------------------
/data/img/background_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/background_0000.gif
--------------------------------------------------------------------------------
/data/img/block_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/block_0000.gif
--------------------------------------------------------------------------------
/data/img/brick_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/brick_0000.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_falling_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_falling_0000.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_falling_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_falling_0001.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_jumping_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_jumping_0000.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_running_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0000.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_running_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0001.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_running_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0002.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_running_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0003.gif
--------------------------------------------------------------------------------
/data/img/bw_mario_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_standing_0000.gif
--------------------------------------------------------------------------------
/data/img/gold_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0000.gif
--------------------------------------------------------------------------------
/data/img/gold_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0001.gif
--------------------------------------------------------------------------------
/data/img/gold_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0002.gif
--------------------------------------------------------------------------------
/data/img/gold_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0003.gif
--------------------------------------------------------------------------------
/data/img/ground_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/ground_0000.gif
--------------------------------------------------------------------------------
/data/img/ground_filler_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/ground_filler_0000.gif
--------------------------------------------------------------------------------
/data/img/mario_falling_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_falling_0000.gif
--------------------------------------------------------------------------------
/data/img/mario_falling_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_falling_0001.gif
--------------------------------------------------------------------------------
/data/img/mario_jumping_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_jumping_0000.gif
--------------------------------------------------------------------------------
/data/img/mario_running_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0000.gif
--------------------------------------------------------------------------------
/data/img/mario_running_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0001.gif
--------------------------------------------------------------------------------
/data/img/mario_running_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0002.gif
--------------------------------------------------------------------------------
/data/img/mario_running_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0003.gif
--------------------------------------------------------------------------------
/data/img/mario_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_standing_0000.gif
--------------------------------------------------------------------------------
/data/img/princess_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/princess_standing_0000.gif
--------------------------------------------------------------------------------
/data/img/question_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/question_0000.gif
--------------------------------------------------------------------------------
/data/map.txt:
--------------------------------------------------------------------------------
1 | BBBBBBBBBBBBBBBBBBBBBB
2 | BBBBBBBBBBBBBBBBBBBBBB
3 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB
4 | BBBBBBBBBBBBBBBBBBBBBB
5 | RRRRR RRRRRR BBBBBBBBBBBBBBBBBBBBBB
6 | BBBBBBBBBBBBBBBBBBBBBB
7 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB
8 | BBBBBBBBBBBBBBBBBBBBBB
9 | RRRR RRRRR OO RRR BBBBBBBBBBBBBBBBBBBBBB
10 | O O RRRBBBBBBBBBBBBBBBBBBBBBB
11 | BBBBBBBBBBBBBBBBBBBBBB
12 | B BBBBBBBBBBBBBBBBBBBBBB
13 | #####################################################################BBBBBBBBBBBBBBBBBBBBB
14 | #####################################################################BBBBBBBBBBBBBBBBBBBBB
15 |
--------------------------------------------------------------------------------
/lib/core.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/core.jar
--------------------------------------------------------------------------------
/lib/gluegen-rt-natives-linux-amd64.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt-natives-linux-amd64.jar
--------------------------------------------------------------------------------
/lib/gluegen-rt-natives-windows-amd64.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt-natives-windows-amd64.jar
--------------------------------------------------------------------------------
/lib/gluegen-rt.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt.jar
--------------------------------------------------------------------------------
/lib/gson-2.8.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gson-2.8.0.jar
--------------------------------------------------------------------------------
/lib/jeromq-0.3.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jeromq-0.3.5.jar
--------------------------------------------------------------------------------
/lib/jl1.0.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jl1.0.1.jar
--------------------------------------------------------------------------------
/lib/jogl-all-natives-linux-amd64.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-linux-amd64.jar
--------------------------------------------------------------------------------
/lib/jogl-all-natives-linux-armv6hf.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-linux-armv6hf.jar
--------------------------------------------------------------------------------
/lib/jogl-all-natives-windows-amd64.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-windows-amd64.jar
--------------------------------------------------------------------------------
/lib/jogl-all.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all.jar
--------------------------------------------------------------------------------
/lib/jsminim.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jsminim.jar
--------------------------------------------------------------------------------
/lib/minim.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/minim.jar
--------------------------------------------------------------------------------
/lib/mp3spi1.9.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/mp3spi1.9.5.jar
--------------------------------------------------------------------------------
/lib/snakeyaml-1.17.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/snakeyaml-1.17.jar
--------------------------------------------------------------------------------
/lib/tritonus_aos.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/tritonus_aos.jar
--------------------------------------------------------------------------------
/lib/tritonus_share.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/tritonus_share.jar
--------------------------------------------------------------------------------
/pde/MarioMultiplay/MarioMultiplay.pde:
--------------------------------------------------------------------------------
1 | import ddf.minim.*;
2 | import java.util.Map;
3 | import java.util.Set;
4 | import java.util.HashSet;
5 | import java.util.ArrayList;
6 | import java.util.LinkedList;
7 | import java.lang.Math;
8 | import org.zeromq.ZMQ;
9 | import org.zeromq.ZMQ.Context;
10 | import org.zeromq.ZMQ.Socket;
11 | import com.google.gson.Gson;
12 | import java.util.concurrent.ConcurrentLinkedQueue;
13 | import org.yaml.snakeyaml.Yaml;
14 |
15 |
16 |
17 |
18 | public void settings() {
19 | size(WIDTH, HEIGHT, P2D);
20 | }
21 |
22 |
23 |
24 |
25 |
26 | enum GAME_STATE {
27 | PLAYING,
28 | WIN,
29 | LOSE
30 | }
31 |
32 | // Global constants
33 |
34 | int FRAME_RATE = 60;
35 | int FRAMES_PER_SHOT = FRAME_RATE / 10;
36 | int WIDTH = 640;
37 | int HEIGHT = 432;
38 | int ASSET_SCALE = 2;
39 | float GRAVITY = 0.7f;
40 | float MARIO_JUMP_FORCE = 12f;
41 | float BOOST_JUMP_FORCE = 4.8f;
42 | float MARIO_VX_LIMIT = 4.7f;
43 | float MARIO_FORCE = 0.2f;
44 | int CAMERA_RANGE_LEFT = 170;
45 | int BLOCK_OFFSET = -16;
46 | String SERVER_HOST;
47 | String XSUB_PORT = "1234";
48 | String XPUB_PORT = "5678";
49 | int MY_MARIO_NUM;
50 | String ROOM_NUM;
51 | float MY_FRICTIONAL_FORCE = 0.4f; // 정상 0.4. 얼음 세상: 0.1
52 | boolean SHOW_PACKET_INDICATOR;
53 |
54 | // Global variables
55 |
56 | GAME_STATE gameState = GAME_STATE.PLAYING;
57 | long frame = 0;
58 | Set pendingKeyboardEvents;
59 | boolean needSync = true;
60 |
61 | // Global objects
62 |
63 | Mario marioMe;
64 | Mario marioRival;
65 | Mario mario0;
66 | Mario mario1;
67 | Sprite bgImgSprite;
68 | Sprite princessSprite;
69 | BlockManager blockManager;
70 | CollisionDetector collisionDetector;
71 | EnemyCollisionDetector enemyCD;
72 | NetworkManager networkManager;
73 | UIManger uiManager;
74 | // Audio ------------- START
75 | AudioPlayer player;
76 | Minim minim; // Audio context
77 | // Audio ------------- END
78 | Gson gson;
79 |
80 | Context pubContext;
81 | Socket pubSocket;
82 | ConcurrentLinkedQueue msgQueue;
83 |
84 |
85 | public void setup() {
86 | background(51);
87 | frameRate(FRAME_RATE);
88 |
89 | // Config file
90 | Yaml yaml = new Yaml();
91 | String[] lines = loadStrings("config.yml");
92 | StringBuilder sb = new StringBuilder();
93 | for (String line: lines) sb.append(line).append("\n");
94 | String document = sb.toString();
95 | Map confMap = (Map) yaml.load(document);
96 | ROOM_NUM = (String) confMap.get("room_num");
97 | System.out.println("ROOM_NUM=" + ROOM_NUM);
98 | MY_MARIO_NUM = (int) confMap.get("my_mario_num");
99 | System.out.println("MY_MARIO_NUM=" + MY_MARIO_NUM);
100 | MY_FRICTIONAL_FORCE = (float) (double) confMap.get("my_frictional_force");
101 | System.out.println("MY_FRICTIONAL_FORCE=" + MY_FRICTIONAL_FORCE);
102 | SERVER_HOST = (String) confMap.get("server_host");
103 | System.out.println("SERVER_HOST=" + SERVER_HOST);
104 | SHOW_PACKET_INDICATOR = (boolean) confMap.get("show_packet_indicator");
105 | System.out.println("SHOW_PACKET_INDICATOR=" + SHOW_PACKET_INDICATOR);
106 |
107 | // Backgroud image
108 | bgImgSprite = new SharedSprite("img/background_", 1);
109 |
110 | // Princess
111 | princessSprite = new SharedSprite("img/princess_standing_", 1);
112 |
113 | // Block manager
114 | blockManager = new BlockManager();
115 |
116 | // Collision detector
117 | collisionDetector = new CollisionDetector();
118 | enemyCD = new EnemyCollisionDetector();
119 |
120 | // Well, our main character appears
121 | marioMe = new Mario(false); // Color
122 | marioRival = new Mario(true); // Black and White
123 |
124 | // marioMe, marioRival
125 | if (MY_MARIO_NUM == 0) {
126 | mario0 = marioMe;
127 | mario1 = marioRival;
128 | } else {
129 | mario1 = marioMe;
130 | mario0 = marioRival;
131 | }
132 | // 마리오1은 마리오0의 오른쪽에
133 | mario1.x = mario0.x + 300;
134 |
135 | // BGM
136 | minim = new Minim(this);
137 | player = minim.loadFile("Supermario_BGM_Overworld.mp3");
138 | player.play();
139 |
140 | // Network manager
141 | networkManager = new NetworkManager();
142 |
143 | // UI manager
144 | uiManager = new UIManger();
145 |
146 | // Gson
147 | gson = new Gson();
148 |
149 | // Publisher
150 | pubContext = ZMQ.context(1);
151 | pubSocket = pubContext.socket(ZMQ.PUB);
152 | pubSocket.connect("tcp://" + SERVER_HOST + ":" + XSUB_PORT);
153 |
154 | // Message queue
155 | msgQueue = new ConcurrentLinkedQueue();
156 |
157 | // Keyboard event queue
158 | pendingKeyboardEvents = new HashSet();
159 |
160 | // Subscriber thread: 꼭 msgQueue 가 초기화 된 후에 구독자 스레드를 론칭 시켜야!
161 | new Thread(new Subscriber()).start();
162 | }
163 |
164 |
165 | public void stop() {
166 | // Audio player
167 | player.close();
168 | minim.stop();
169 |
170 | // ZeroMQ publisher
171 | pubSocket.close();
172 | pubContext.term();
173 |
174 | super.stop();
175 | }
176 |
177 |
178 | public void draw() {
179 | processUserInput();
180 |
181 | processGameLogic();
182 |
183 | doNetworkStuff();
184 |
185 | uiManager.update();
186 |
187 | render();
188 |
189 | ++frame;
190 | }
191 |
192 |
193 | // ============================= USER INPUT ================================
194 |
195 |
196 | public void processUserInput() {
197 | marioMe.processKeyboard();
198 | }
199 |
200 | /**
201 | * Called once every time a key is pressed. The key that was pressed is
202 | * stored in the key variable.
203 | */
204 | public void keyPressed() {
205 | marioMe.onKeyPress(key);
206 | }
207 |
208 | /**
209 | * Called once every time a key is released. The key that was released will
210 | * be stored in the key variable.
211 | */
212 | public void keyReleased() {
213 | marioMe.onKeyRelease(key);
214 | }
215 |
216 |
217 | // ============================= GAME LOGIC ================================
218 |
219 |
220 | public void processGameLogic() {
221 | // Game over?
222 | if (gameState != GAME_STATE.PLAYING) return;
223 |
224 | // Update the shared sprites
225 | blockManager.spriteGold.update();
226 |
227 | // Do the simulation
228 | marioMe.update();
229 | marioRival.update();
230 | }
231 |
232 |
233 | // ======================== NETWORK COMMUNICATION ===========================
234 |
235 |
236 | public void doNetworkStuff() {
237 | networkWrite(); // 먼저 조립된 내 마리오 상태 보내고
238 | networkRead(); // 고스트 마리오를 세팅하자
239 | }
240 |
241 | public void networkRead() {
242 | // Game over?
243 | if (gameState != GAME_STATE.PLAYING) return;
244 |
245 | // Process all unread msgs
246 | ArrayList unreadMsgs = networkManager.getUnreadMsgs();
247 | for (String msg: unreadMsgs) {
248 | if (gameState == GAME_STATE.PLAYING) {
249 | processMsg(msg);
250 | uiManager.addInPacket();
251 | } else { // Game over
252 | ; // Throw packet
253 | }
254 | }
255 | }
256 |
257 | public void networkWrite() {
258 | // Game over?
259 | if (gameState != GAME_STATE.PLAYING) return;
260 |
261 | // Need sync?
262 | if (! needSync) return;
263 |
264 | Packet packet = networkManager.makeMarioStatePacket(marioMe);
265 | String packetStr = networkManager.packetToString(packet);
266 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM;
267 | pubSocket.sendMore(channel);
268 | pubSocket.send(packetStr);
269 |
270 | // Notify UIManager
271 | uiManager.addOutPacket();
272 | }
273 |
274 | public void processMsg(String msg) {
275 | if (frame < 100) System.out.println("Frame " + frame + ": " + msg);
276 |
277 | Packet packet = networkManager.stringToPacket(msg);
278 |
279 | // Judge packet type
280 | switch (packet.type) {
281 | case "GAME_OVER":
282 | if (packet.whoWon == MY_MARIO_NUM) { // Win
283 | gameState = GAME_STATE.WIN;
284 | } else if (packet.whoWon == (1 - MY_MARIO_NUM)) { // Lose
285 | gameState = GAME_STATE.LOSE;
286 | }
287 | break;
288 | case "MARIO_STATE":
289 | // 마리오 상태 세팅
290 | marioRival.x = packet.x;
291 | marioRival.y = packet.y;
292 | marioRival.vx = packet.vx;
293 | marioRival.vy = packet.vy;
294 | marioRival.ax = packet.ax;
295 | marioRival.ay = packet.ay;
296 | switch (packet.motionState) {
297 | case "STANDING": marioRival.motionState = MotionState.STANDING;
298 | break;
299 | case "RUNNING": marioRival.motionState = MotionState.RUNNING;
300 | break;
301 | case "JUMPING": marioRival.motionState = MotionState.JUMPING;
302 | break;
303 | case "FALLING": marioRival.motionState = MotionState.FALLING;
304 | break;
305 | default:
306 | break;
307 | }
308 | switch (packet.faceState) {
309 | case "FACE_FRONT": marioRival.faceState = MarioFace.FACE_FRONT;
310 | break;
311 | case "FACE_LEFT": marioRival.faceState = MarioFace.FACE_LEFT;
312 | break;
313 | case "FACE_RIGHT": marioRival.faceState = MarioFace.FACE_RIGHT;
314 | break;
315 | default:
316 | break;
317 | }
318 | marioRival.boostJumping = packet.boostJumping;
319 | marioRival.jumpPressed = packet.jumpPressed;
320 | switch (packet.arrowX) {
321 | case "LEFT": marioRival.arrowX = MarioArrowX.LEFT;
322 | break;
323 | case "RIGHT": marioRival.arrowX = MarioArrowX.RIGHT;
324 | break;
325 | default: marioRival.arrowX = MarioArrowX.NONE;
326 | break;
327 | }
328 | switch (packet.arrowY) {
329 | case "UP": marioRival.arrowY = MarioArrowY.UP;
330 | break;
331 | case "DOWN": marioRival.arrowY = MarioArrowY.DOWN;
332 | break;
333 | default: marioRival.arrowY = MarioArrowY.NONE;
334 | break;
335 | }
336 | marioRival.frictionalForce = packet.frictionalForce;
337 | break;
338 | default: ;
339 | break;
340 | }
341 | }
342 |
343 |
344 | public void gameOverWinning() {
345 | gameState = GAME_STATE.WIN;
346 |
347 | // Send GAME_OVER packet to the rival
348 | Packet packet = networkManager.makeGameOverPacket(MY_MARIO_NUM);
349 | String packetStr = networkManager.packetToString(packet);
350 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM;
351 | pubSocket.sendMore(channel);
352 | pubSocket.send(packetStr);
353 | }
354 |
355 | public void resetGame() {
356 | gameState = GAME_STATE.PLAYING;
357 | marioMe.x = 150 + MY_MARIO_NUM * 300;
358 | marioMe.y = 0;
359 | marioMe.vx = 0;
360 | marioMe.vy = 0;
361 | marioMe.ax = 0;
362 | marioMe.ay = GRAVITY;
363 | needSync = true;
364 | msgQueue.clear();
365 | }
366 |
367 |
368 | // ============================= RENDERING =================================
369 |
370 |
371 | public void render() {
372 | // Draw Background
373 | drawBackground();
374 |
375 | // Draw map
376 | drawMap();
377 |
378 | // Draw the Marios' sprite
379 | noFill();
380 | noStroke();
381 | marioRival.display();
382 | marioMe.display();
383 |
384 | // Draw UI
385 | uiManager.display();
386 |
387 | // Game Over?
388 | textSize(50);
389 | fill(255, 174, 201);
390 | textAlign(CENTER, CENTER);
391 | if (gameState == GAME_STATE.WIN) {
392 | String gameStateStr = "WIN";
393 | text(gameStateStr, WIDTH / 2, HEIGHT / 2);
394 | } else if (gameState == GAME_STATE.LOSE) {
395 | String gameStateStr = "LOSE";
396 | text(gameStateStr, WIDTH / 2, HEIGHT / 2);
397 | }
398 | }
399 |
400 | public void drawBackground() {
401 | fill(107, 140, 255);
402 | noStroke();
403 | rect(0, 0, WIDTH, HEIGHT);
404 | bgImgSprite.display(-1, 8);
405 | princessSprite.display(6639, 384 - princessSprite.getHeight());
406 | }
407 |
408 | public void drawMap() {
409 | blockManager.display();
410 | }
411 |
412 |
413 |
414 |
415 |
416 | enum MotionState {
417 | STANDING, RUNNING, JUMPING, FALLING,
418 | }
419 |
420 | enum MarioFace {
421 | FACE_LEFT, FACE_RIGHT, FACE_FRONT
422 | }
423 |
424 | enum MarioArrowX {
425 | NONE,
426 | LEFT,
427 | RIGHT
428 | }
429 |
430 | enum MarioArrowY {
431 | NONE,
432 | UP,
433 | DOWN
434 | }
435 |
436 | class Mario {
437 | // Space state
438 | int width, height;
439 | float x = 150, y = 0;
440 | float vx = 0;
441 | float vy = 0;
442 | float ax = 0;
443 | float ay = GRAVITY;
444 |
445 | // Force state
446 | float frictionalForce = 0;
447 |
448 | // Motion state
449 | MotionState motionState = MotionState.JUMPING;
450 | MarioFace faceState = MarioFace.FACE_RIGHT;
451 |
452 | // Keyboard State
453 | MarioArrowX arrowX = MarioArrowX.NONE;
454 | MarioArrowY arrowY = MarioArrowY.NONE;
455 | boolean jumpPressed = false;
456 |
457 | boolean boostJumping = false;
458 |
459 | // Sprite state
460 | Sprite currentSprite = null;
461 |
462 | // Sprites
463 | Sprite marioStanding, marioRunning, marioJumping, marioFalling;
464 |
465 | Camera camera;
466 |
467 | boolean isGhost = false;
468 |
469 | Mario(boolean blackWhite) {
470 | // Load sprites
471 | if (! blackWhite) {
472 | marioStanding = new Sprite("img/mario_standing_", 1, true);
473 | marioRunning = new Sprite("img/mario_running_", 4, true);
474 | marioJumping = new Sprite("img/mario_jumping_", 1, true);
475 | marioFalling = new Sprite("img/mario_falling_", 2, true);
476 | } else {
477 | marioStanding = new Sprite("img/bw_mario_standing_", 1, true);
478 | marioRunning = new Sprite("img/bw_mario_running_", 4, true);
479 | marioJumping = new Sprite("img/bw_mario_jumping_", 1, true);
480 | marioFalling = new Sprite("img/bw_mario_falling_", 2, true);
481 |
482 | isGhost = true;
483 | }
484 |
485 | width = marioStanding.getWidth();
486 | height = marioStanding.getHeight();
487 |
488 | frictionalForce = MY_FRICTIONAL_FORCE;
489 |
490 | // Make an own camera
491 | camera = new Camera();
492 | }
493 |
494 | public void update() {
495 | _updateMotion();
496 | _updateSpeed();
497 | _updatePosition();
498 | }
499 |
500 | public void display() {
501 | // Select sprite
502 | if (motionState == MotionState.FALLING) currentSprite = marioFalling;
503 | else if (motionState == MotionState.JUMPING) currentSprite = marioJumping;
504 | else if (motionState == MotionState.RUNNING) currentSprite = marioRunning;
505 | else if (motionState == MotionState.STANDING) currentSprite = marioStanding;
506 |
507 | // Select face direction
508 | if (faceState == MarioFace.FACE_LEFT) {
509 | currentSprite.display(x, y, true);
510 | } else
511 | currentSprite.display(x, y);
512 | }
513 |
514 | public float getCenterX() {
515 | return x + 0.5f * width;
516 | }
517 |
518 | // 히트박스 위의 두 정점
519 | public Coordinate[] getHitboxTop() {
520 | Coordinate vertx1 = new Coordinate(x + 2, y);
521 | Coordinate vertx2 = new Coordinate(x + width - 2, y);
522 | return new Coordinate[]{vertx1, vertx2};
523 | }
524 |
525 | // 히트박스 아래의 두 정점
526 | public Coordinate[] getHitboxBottom() {
527 | Coordinate vertx1 = new Coordinate(x + 2, y + height);
528 | Coordinate vertx2 = new Coordinate(x + width - 2, y + height);
529 | return new Coordinate[]{vertx1, vertx2};
530 | }
531 |
532 | public Hitbox getHitboxHead() {
533 | Hitbox hitbox = new Hitbox();
534 | hitbox.topLeft = new Coordinate(x + 2, y);
535 | hitbox.topRight = new Coordinate(x + width - 2, y);
536 | hitbox.bottomLeft = new Coordinate(x + 2, y + height - 25);
537 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height - 25);
538 | return hitbox;
539 | }
540 |
541 | public Hitbox getHitbox() {
542 | Hitbox hitbox = new Hitbox();
543 | hitbox.topLeft = new Coordinate(x + 2, y);
544 | hitbox.topRight = new Coordinate(x + width - 2, y);
545 | hitbox.bottomLeft = new Coordinate(x + 2, y + height);
546 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height);
547 | return hitbox;
548 | }
549 |
550 | public void onKeyPress(char key) {
551 | if (key == 'c' || key == 'C') {
552 | pendingKeyboardEvents.add("_jump");
553 | } else if (key == 'x' || key == '#') {
554 | ;
555 | } else if (key == ENTER) {
556 | if (gameState != GAME_STATE.PLAYING) resetGame();
557 | }else if (key == CODED) {
558 | if (keyCode == UP) {
559 | pendingKeyboardEvents.add("_up");
560 | } else if (keyCode == DOWN) {
561 | pendingKeyboardEvents.add("_down");
562 | }
563 | if (keyCode == LEFT) {
564 | pendingKeyboardEvents.add("_left");
565 | } else if (keyCode == RIGHT) {
566 | pendingKeyboardEvents.add("_right");
567 | }
568 | }
569 | }
570 |
571 | public void onKeyRelease(char key) {
572 | if (key == 'c' || key == 'C') {
573 | pendingKeyboardEvents.add("_release_jump");
574 | } else if (key == 'x' || key == '#') {
575 | ;
576 | } else if (key == CODED) {
577 | if (keyCode == UP) {
578 | pendingKeyboardEvents.add("_release_up");
579 | } else if (keyCode == DOWN) {
580 | pendingKeyboardEvents.add("_release_down");
581 | }
582 | if (keyCode == LEFT) {
583 | pendingKeyboardEvents.add("_release_left");
584 | } else if (keyCode == RIGHT) {
585 | pendingKeyboardEvents.add("_release_right");
586 | }
587 | }
588 | }
589 |
590 | public void processKeyboard() {
591 | if (pendingKeyboardEvents.isEmpty()) needSync = false;
592 | else needSync = true;
593 |
594 | for (String event: pendingKeyboardEvents) {
595 | switch (event) {
596 | case "_jump": _jump();
597 | break;
598 | case "_left": _left();
599 | break;
600 | case "_right": _right();
601 | break;
602 | case "_up": arrowY = MarioArrowY.UP;
603 | break;
604 | case "_down": arrowY = MarioArrowY.DOWN;
605 | break;
606 | case "_release_jump": jumpPressed = false;
607 | break;
608 | case "_release_left": if (arrowX == MarioArrowX.LEFT) arrowX = MarioArrowX.NONE;;
609 | break;
610 | case "_release_right": if (arrowX == MarioArrowX.RIGHT) arrowX = MarioArrowX.NONE;
611 | break;
612 | case "_release_up": if (arrowY == MarioArrowY.UP) arrowY = MarioArrowY.NONE;
613 | break;
614 | case "_release_down": if (arrowY == MarioArrowY.DOWN) arrowY = MarioArrowY.NONE;
615 | break;
616 | default: ;
617 | break;
618 | }
619 | }
620 | pendingKeyboardEvents.clear();
621 | }
622 |
623 | public void _jump() {
624 | if (motionState != MotionState.JUMPING) {
625 | motionState = MotionState.JUMPING;
626 | vy = -MARIO_JUMP_FORCE; // 위로 튕겨준다
627 | }
628 | jumpPressed = true;
629 | }
630 |
631 | public void _boostJump() {
632 | if (! boostJumping) {
633 | boostJumping = true;
634 | vy -= BOOST_JUMP_FORCE;
635 | }
636 | }
637 |
638 | public void _disJump() {
639 | // 점핑 상태 해제
640 | if (motionState == MotionState.JUMPING) motionState = MotionState.STANDING;
641 | boostJumping = false;
642 | vy = 0;
643 | }
644 |
645 | public void _friction() {
646 | // 마찰하여 감속
647 | if (vx > 0 && motionState != MotionState.JUMPING) ax = -frictionalForce;
648 | else if (vx < 0 && motionState != MotionState.JUMPING) ax = frictionalForce;
649 | }
650 |
651 | public void _left() {
652 | arrowX = MarioArrowX.LEFT;
653 |
654 | // Turn the face to left
655 | faceState = MarioFace.FACE_LEFT;
656 | // 서있던거라면 달려라
657 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING;
658 |
659 | // Give a force to left
660 | if (vx > 0) vx = 0;
661 | ax = -MARIO_FORCE;
662 | }
663 |
664 | public void _right() {
665 | arrowX = MarioArrowX.RIGHT;
666 |
667 | // Turn the face to right
668 | faceState = MarioFace.FACE_RIGHT;
669 | // 서있던거라면 달려라
670 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING;
671 |
672 | // Give a force to right
673 | if (vx < 0) vx = 0;
674 | ax = MARIO_FORCE;
675 | }
676 |
677 | public void _updateMotion() {
678 | if (arrowX == MarioArrowX.LEFT) {
679 | faceState = MarioFace.FACE_LEFT;
680 |
681 | if (motionState == MotionState.STANDING) { // 달리기 로직
682 | motionState = MotionState.RUNNING;
683 | }
684 | } else if (arrowX == MarioArrowX.RIGHT) {
685 | faceState = MarioFace.FACE_RIGHT;
686 | if (motionState == MotionState.STANDING) { // 달리기 로직
687 | motionState = MotionState.RUNNING;
688 | }
689 | } else if (arrowX == MarioArrowX.NONE) {
690 | // 멈추기 로직
691 | if (motionState == MotionState.RUNNING) motionState = MotionState.STANDING;
692 | _friction();
693 | }
694 | }
695 |
696 | public void _updateSpeed() {
697 | // Y-axis
698 | vy += ay;
699 | // 부스트 점프
700 | if (vy < 0 && vy > -4 && jumpPressed && ! boostJumping) _boostJump();
701 |
702 | // X-axis
703 | float previousVx = vx;
704 | vx += ax;
705 | // Limit speed
706 | if (Math.abs(vx) > MARIO_VX_LIMIT) {
707 | if (vx > 0) {
708 | vx = MARIO_VX_LIMIT;
709 | } else {
710 | vx = -MARIO_VX_LIMIT;
711 | }
712 | } else if (Math.abs(vx) < MARIO_FORCE) {
713 | vx = 0;
714 | ax = 0;
715 | } else if (previousVx * vx < 0) { // Symbol changed
716 | vx = 0;
717 | ax = 0;
718 | }
719 | }
720 |
721 | public void _updatePosition() {
722 | // Game Over?
723 | if (gameState != GAME_STATE.PLAYING) return;
724 |
725 | // Update X
726 | float previousX = x;
727 | x += vx;
728 | if (x < 0) x = 0;
729 | if (x > 7000) x = 7000;
730 |
731 | // Collision detection: X
732 | if (collisionDetector.left(this) || collisionDetector.right(this)){
733 | x = previousX;
734 | vx = 0;
735 | }
736 |
737 | // Update Y
738 | float previousY = y;
739 | y += vy;
740 |
741 | // Collision detection: Y
742 | if (collisionDetector.up(this)) {
743 | y = previousY;
744 | vy = 0;
745 | }
746 | if (y > HEIGHT - mario0.height || collisionDetector.down(this) ) {
747 | y = previousY;
748 | vy = 0;
749 | _disJump();
750 | }
751 |
752 | // Enemy Collision Detection
753 | if (! isGhost) { // FTS: Favor The Shooter, 고스트는 밟기 판정을 안함
754 | if (enemyCD.isTrample(getHitboxBottom(), marioRival.getHitboxHead(), this)
755 | == HitType.ENEMY) {
756 | gameOverWinning();
757 | }
758 | }
759 |
760 | // Camera following
761 | if (getCenterX() - camera.getCenterX() > 0) { // 마리오 너무 오른 쪽
762 | camera.x = getCenterX() - 0.5f * camera.width;
763 | } else if (camera.getCenterX() - getCenterX() > CAMERA_RANGE_LEFT) { // 마리오 너무 왼쪽
764 | camera.x = getCenterX() + CAMERA_RANGE_LEFT - 0.5f * camera.width;
765 | if (camera.x < 0) camera.x = 0;
766 | }
767 | }
768 | }
769 |
770 |
771 |
772 | int framesPerShot = FRAMES_PER_SHOT;
773 | int spriteScale = ASSET_SCALE;
774 |
775 | /**
776 | * Class for animating a sequence of GIFs
777 | */
778 | class Sprite {
779 | PImage[] images;
780 | PImage[] mirroredImages;
781 | int imageCount;
782 | int frame;
783 |
784 | Sprite(String imagePrefix, int count) {
785 | imageCount = count;
786 | images = new PImage[imageCount];
787 |
788 | for (int i = 0; i < imageCount; ++i) {
789 | // Use nf() to number format 'i' into four digits
790 | String filename = imagePrefix + nf(i, 4) + ".gif";
791 | PImage img = loadImage(filename);
792 | images[i] = _enlarge(img, 2);
793 | }
794 | }
795 |
796 | Sprite(String imagePrefix, int count, boolean hasMirrored) {
797 |
798 | imageCount = count;
799 | images = new PImage[imageCount];
800 |
801 | for (int i = 0; i < imageCount; ++i) {
802 | // Use nf() to number format 'i' into four digits
803 | String filename = imagePrefix + nf(i, 4) + ".gif";
804 | PImage img = loadImage(filename);
805 | images[i] = _enlarge(img, 2);
806 | }
807 |
808 | if (hasMirrored) {
809 | mirroredImages = new PImage[imageCount];
810 | for (int i = 0; i < imageCount; ++i) {
811 | mirroredImages[i] = _flip(images[i]);
812 | }
813 | }
814 | }
815 |
816 | public void display(float xpos, float ypos) { // 월드 좌표 받고
817 | frame = (frame + 1) % (imageCount * framesPerShot);
818 | int shotIndex = frame / framesPerShot;
819 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, images[shotIndex].width,
820 | images[shotIndex].height); // 카메라 좌표계로 그려줌
821 | }
822 |
823 | public void display(float xpos, float ypos, boolean reverse) {
824 | if (! reverse) display(xpos - marioMe.camera.x, ypos);
825 |
826 | frame = (frame + 1) % (imageCount * framesPerShot);
827 | int shotIndex = frame / framesPerShot;
828 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, mirroredImages[shotIndex].width,
829 | mirroredImages[shotIndex].height);
830 | }
831 |
832 | public int getWidth() {
833 | return images[0].width;
834 | }
835 |
836 | public int getHeight() {
837 | return images[0].height;
838 | }
839 |
840 | public PImage _enlarge(PImage image, int multiple) {
841 | PImage newImg;
842 | newImg = createImage(image.width * multiple, image.height * multiple, ARGB);
843 | for (int i = 0; i < image.width; ++i) {
844 | for (int j = 0; j < image.height; ++j) {
845 | newImg.set(2 * i, 2 * j, image.get(i, j));
846 | newImg.set(2 * i, 2 * j + 1, image.get(i, j));
847 | newImg.set(2 * i + 1, 2 * j, image.get(i, j));
848 | newImg.set(2 * i + 1, 2 * j + 1, image.get(i, j));
849 | }
850 | }
851 |
852 | return newImg;
853 | }
854 |
855 | public PImage _flip(PImage image) {
856 | PImage reverse;
857 | reverse = createImage(image.width, image.height, ARGB);
858 |
859 | for (int i = 0; i < image.width; ++i) {
860 | for (int j = 0; j < image.height; ++j) {
861 | int xPixel, yPixel;
862 | xPixel = image.width - 1 - i;
863 | yPixel = j;
864 | reverse.pixels[yPixel * image.width + xPixel] = image.pixels[j * image.width + i];
865 | }
866 | }
867 | return reverse;
868 | }
869 |
870 | }
871 |
872 | class SharedSprite extends Sprite {
873 |
874 | public SharedSprite(String imagePrefix, int count) {
875 | super(imagePrefix, count);
876 | }
877 |
878 | public SharedSprite(String imagePrefix, int count, boolean hasMirrored) {
879 | super(imagePrefix, count, hasMirrored);
880 | }
881 |
882 | public void display(float xpos, float ypos) {
883 | int shotIndex = frame / framesPerShot;
884 | image(images[shotIndex], xpos - marioMe.camera.x, ypos,
885 | images[shotIndex].width, images[shotIndex].height);
886 | }
887 |
888 | public void display(float xpos, float ypos, boolean reverse) {
889 | int shotIndex = frame / framesPerShot;
890 |
891 | if (! reverse) {
892 | image(images[shotIndex], xpos - marioMe.camera.x, ypos,
893 | images[shotIndex].width, images[shotIndex].height);
894 | } else {
895 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos,
896 | mirroredImages[shotIndex].width, mirroredImages[shotIndex].height);
897 | }
898 | }
899 |
900 | public void update() {
901 | frame = (frame + 1) % (imageCount * framesPerShot);
902 | }
903 | }
904 |
905 | class TextSprite {
906 | private float x, y;
907 | private float vy;
908 | private String _text;
909 | private int size;
910 | private int frame;
911 |
912 | public TextSprite(String text, int size, float x, float y, float vy) {
913 | this.x = x;
914 | this.y = y;
915 | this._text = text;
916 | this.size = size;
917 | this.vy = vy;
918 | }
919 |
920 | public void update() {
921 | this.y += vy;
922 | ++frame;
923 | }
924 |
925 | public void display() {
926 | textSize(size);
927 | fill(255, 255, 255);
928 | textAlign(CENTER, CENTER);
929 | text(_text, x, y);
930 | }
931 | }
932 |
933 |
934 |
935 |
936 |
937 | class Camera {
938 | int width, height; // Camera sight
939 | float x = 0, y = 0; // Camera's absolute coordinate
940 |
941 | Camera() {
942 | width = WIDTH;
943 | height = HEIGHT;
944 | }
945 |
946 | float getCenterX() {
947 | return x + 0.5f * width;
948 | }
949 | }
950 |
951 |
952 |
953 |
954 |
955 | class Block {
956 | int x = 0, y = 0;
957 | int xB = 0, yB = 0;
958 | char blockType;
959 | boolean visible = true;
960 | SharedSprite sprite;
961 |
962 | public Block(int xB, int yB, char blockType) {
963 | this.xB = xB;
964 | this.yB = yB;
965 | this.x = 16 * ASSET_SCALE * xB + BLOCK_OFFSET;
966 | this.y = 16 * ASSET_SCALE * yB;
967 |
968 | this.visible = true;
969 | this.blockType = blockType;
970 | }
971 |
972 | public void display() {
973 | if (sprite != null) {
974 | sprite.display(x, y); // 월드 좌표 받음
975 | }
976 | }
977 | }
978 |
979 | class Coordinate {
980 | float x = 0, y = 0;
981 |
982 | public Coordinate(float x, float y) {
983 | this.x = x;
984 | this.y = y;
985 | }
986 | }
987 |
988 | class BlockManager {
989 | SharedSprite spriteGround;
990 | SharedSprite spriteBrick;
991 | SharedSprite spriteBlock;
992 | SharedSprite spriteQuestion;
993 | SharedSprite spriteGold;
994 | ArrayList> blocks = new ArrayList>();
995 |
996 | public BlockManager() {
997 | // Load sprites
998 | spriteGround = new SharedSprite("img/ground_", 1);
999 | spriteBlock = new SharedSprite("img/block_", 1);
1000 | spriteBrick = new SharedSprite("img/brick_", 1);
1001 | spriteQuestion = new SharedSprite("img/question_", 1);
1002 | spriteGold = new SharedSprite("img/gold_", 4);
1003 |
1004 | // Read map.txt
1005 | String[] lines = loadStrings("map.txt");
1006 | // Make blocks
1007 | println("Start to make blocks..");
1008 | for (int i = 0; i < lines.length; ++i) {
1009 | String line = lines[i];
1010 | char[] charArray = line.toCharArray();
1011 | ArrayList blockRow = new ArrayList();
1012 | for (int j = 0; j < charArray.length; ++j) {
1013 | char c = charArray[j];
1014 | Block block = new Block(j, i, c);
1015 | switch (c) {
1016 | case ' ' :
1017 | break;
1018 | case '#' : ; block.sprite = spriteGround; // Ground
1019 | break;
1020 | case 'B' : block.sprite = spriteBlock; // Block
1021 | break;
1022 | case 'R' : block.sprite = spriteBrick; // Block
1023 | break;
1024 | case '?' : block.sprite = spriteQuestion; // Question mark
1025 | break;
1026 | case 'O' : block.sprite = spriteGold; // Gold
1027 | break;
1028 | default :
1029 | break;
1030 | }
1031 |
1032 | blockRow.add(block);
1033 | print(c);
1034 | }
1035 | blocks.add(blockRow);
1036 | println();
1037 | }
1038 | }
1039 |
1040 | public void display() {
1041 | int xStart;
1042 | if ((x2bX(marioMe.camera.getCenterX()) - 11) < 0) xStart = 0;
1043 | else xStart = x2bX(marioMe.camera.getCenterX()) - 11;
1044 | int xEnd = xStart + 23;
1045 |
1046 | for (int i = 0; i < blocks.size(); ++i) {
1047 | for (int j = xStart; j < Math.min(xEnd + 1, blocks.get(i).size()); ++j) {
1048 | Block block = blocks.get(i).get(j);
1049 | block.display();
1050 | }
1051 | }
1052 | }
1053 |
1054 | public Coordinate block2Coor(int bX, int bY) {
1055 | Coordinate coordinate = new Coordinate(16 * ASSET_SCALE * bX,
1056 | 16 * ASSET_SCALE * bY);
1057 | return coordinate;
1058 | }
1059 |
1060 | public int y2bY(float y) {
1061 | return (int) y / (16 * ASSET_SCALE);
1062 | }
1063 |
1064 | public int x2bX(float x) {
1065 | return (int) (x - BLOCK_OFFSET) / (16 * ASSET_SCALE);
1066 | }
1067 | }
1068 |
1069 |
1070 |
1071 |
1072 |
1073 | class CollisionDetector {
1074 | public boolean up(Mario mario) {
1075 | if ((int)mario.y <= 0) return false;
1076 |
1077 | int step = 24;
1078 | for (int i = 0; i < 2; ++i) {
1079 | float x = mario.x + i * step;
1080 | int bX = blockManager.x2bX(x);
1081 | int bY = blockManager.y2bY(mario.y);
1082 |
1083 | ArrayList blockRow = blockManager.blocks.get(bY);
1084 | if (blockRow.size() <= bX) continue; // 블록이 없네
1085 |
1086 | Block block = blockRow.get(bX);
1087 | if (block == null) continue;
1088 | if (block.blockType == ' ' || block.blockType == 'O')
1089 | continue;
1090 | else return true;
1091 | }
1092 |
1093 | // 위에서 true 로 미리 빠져나가지 않았다
1094 | return false;
1095 | }
1096 |
1097 | public boolean down(Mario mario) {
1098 | if ((int)mario.y <= 0) return false;
1099 |
1100 | int step = 24;
1101 | for (int i = 0; i < 2; ++i) {
1102 | float x = mario.x + i * step;
1103 | int bX = blockManager.x2bX(x);
1104 | int bY = blockManager.y2bY(mario.y + mario.height);
1105 |
1106 | ArrayList blockRow = blockManager.blocks.get(bY);
1107 | if (blockRow.size() <= bX) continue; // 블록이 없네
1108 |
1109 | Block block = blockRow.get(bX);
1110 | if (block == null) continue;
1111 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1112 | else return true;
1113 | }
1114 |
1115 | // 위에서 true 로 미리 빠져나가지 않았다
1116 | return false;
1117 | }
1118 |
1119 | public boolean left(Mario mario) {
1120 | if ((int)mario.y <= 0) return false;
1121 |
1122 | int step = mario.height / 2;
1123 | for (int i = 0; i < 3; ++i) {
1124 | float y = mario.y + i * step;
1125 | int bX = blockManager.x2bX(mario.x);
1126 | int bY = blockManager.y2bY(y);
1127 |
1128 | ArrayList blockRow = blockManager.blocks.get(bY);
1129 | if (blockRow.size() <= bX) continue; // 블록이 없네
1130 |
1131 | Block block = blockRow.get(bX);
1132 | if (block == null) continue;
1133 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1134 | else return true;
1135 | }
1136 |
1137 | // 위에서 true 로 미리 빠져나가지 않았다
1138 | return false;
1139 | }
1140 |
1141 | public boolean right(Mario mario) {
1142 | if ((int)mario.y <= 0) return false;
1143 |
1144 | int step = mario.height / 2;
1145 | for (int i = 0; i < 3; ++i) {
1146 | float y = mario.y + i * step;
1147 | int bX = blockManager.x2bX(mario.x + mario.width);
1148 | int bY = blockManager.y2bY(y);
1149 |
1150 | ArrayList blockRow = blockManager.blocks.get(bY);
1151 | if (blockRow.size() <= bX) continue; // 블록이 없네
1152 |
1153 | Block block = blockRow.get(bX);
1154 | if (block == null) continue;
1155 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1156 | else return true;
1157 | }
1158 |
1159 | // 위에서 true 로 미리 짜져나가지 않았다
1160 | return false;
1161 | }
1162 | }
1163 |
1164 |
1165 |
1166 |
1167 |
1168 | class Rect {
1169 | Coordinate topLeft;
1170 | Coordinate topRight;
1171 | Coordinate bottomLeft;
1172 | Coordinate bottomRight;
1173 | }
1174 |
1175 | class Hitbox extends Rect {}
1176 |
1177 | enum HitType {
1178 | NONE,
1179 | ENEMY
1180 | }
1181 |
1182 | class EnemyCollisionDetector {
1183 | public HitType isTrample(Coordinate[] myHitboxBottom, Hitbox enemyHitbox,
1184 | Mario marioMe) {
1185 | if (marioMe.vy < 0.1) return HitType.NONE;
1186 |
1187 | Coordinate[] hbVertexes = new Coordinate[]{
1188 | enemyHitbox.topLeft,
1189 | enemyHitbox.topRight,
1190 | enemyHitbox.bottomLeft,
1191 | enemyHitbox.bottomRight
1192 | };
1193 | for (Coordinate foot: myHitboxBottom) {
1194 | boolean isMinX = true;
1195 | for (Coordinate vertx: hbVertexes) {
1196 | if (foot.x >= vertx.x) {
1197 | isMinX = false;
1198 | break;
1199 | }
1200 | }
1201 | if (isMinX) continue;
1202 |
1203 | boolean isMaxX = true;
1204 | for (Coordinate vertx: hbVertexes) {
1205 | if (foot.x <= vertx.x) {
1206 | isMaxX = false;
1207 | break;
1208 | }
1209 | }
1210 | if (isMaxX) continue;
1211 |
1212 | boolean isMinY = true;
1213 | for (Coordinate vertx: hbVertexes) {
1214 | if (foot.y >= vertx.y) {
1215 | isMinY = false;
1216 | break;
1217 | }
1218 | }
1219 | if (isMinY) continue;
1220 |
1221 | boolean isMaxY = true;
1222 | for (Coordinate vertx: hbVertexes) {
1223 | if (foot.y <= vertx.y) {
1224 | isMaxY = false;
1225 | break;
1226 | }
1227 | }
1228 | if (isMaxY) continue;
1229 |
1230 | // Neither min nor max, return HIT
1231 | return HitType.ENEMY;
1232 | }
1233 |
1234 | // Not hit
1235 | return HitType.NONE;
1236 | }
1237 | }
1238 |
1239 |
1240 |
1241 |
1242 |
1243 | class Subscriber implements Runnable {
1244 | Context context;
1245 | Socket subSocket;
1246 |
1247 | // Constructor
1248 | public Subscriber () {
1249 | context = ZMQ.context(1);
1250 | subSocket = context.socket(ZMQ.SUB);
1251 | subSocket.connect("tcp://" + SERVER_HOST + ":" + XPUB_PORT);
1252 | String channel = (ROOM_NUM + "CHANNEL_MARIO_" + String.valueOf(1 - MY_MARIO_NUM));
1253 | // String channel = (ROOM_NUM + "CHANNEL_MARIO_0");
1254 |
1255 | subSocket.subscribe(channel.getBytes(ZMQ.CHARSET));
1256 | }
1257 |
1258 | public void run() {
1259 | while (!Thread.currentThread().isInterrupted()) {
1260 | /* 동기적으로 읽기를 해서, sleep 안해줘도 된다. 샘플코드에서 따온 코드임
1261 | */
1262 |
1263 | // Read envelope with address
1264 | String channel = subSocket.recvStr();
1265 | // Read message contents
1266 | String contents = subSocket.recvStr();
1267 | //System.out.println("Received from: " + channel + " Contents: " + contents);
1268 | msgQueue.add(contents);
1269 | }
1270 | }
1271 |
1272 | public void finallize() throws Throwable {
1273 | try{
1274 | if (subSocket != null) subSocket.close();
1275 | if (context != null) context.term();
1276 | }
1277 | finally {
1278 | super.finalize();
1279 | }
1280 | }
1281 | }
1282 |
1283 |
1284 |
1285 |
1286 |
1287 | class Packet {
1288 | public String type; // "MARIO_STATE" / "GAME_OVER"
1289 | public int whoWon; // 0, 1, -1
1290 | public float x;
1291 | public float y;
1292 | public float vx;
1293 | public float vy;
1294 | public float ax;
1295 | public float ay;
1296 | public String motionState;
1297 | public String faceState;
1298 | public boolean boostJumping;
1299 | public boolean jumpPressed;
1300 | public String arrowX;
1301 | public String arrowY;
1302 | public float frictionalForce;
1303 | }
1304 |
1305 |
1306 |
1307 |
1308 |
1309 | class NetworkManager {
1310 | public ArrayList getUnreadMsgs() {
1311 | ArrayList unreadMsgs = new ArrayList();
1312 | String msg;
1313 | while ((msg = msgQueue.poll()) != null) {
1314 | unreadMsgs.add(msg);
1315 | }
1316 | return unreadMsgs;
1317 | }
1318 |
1319 | public Packet makeMarioStatePacket(Mario mario) {
1320 | Packet packet = new Packet();
1321 | packet.type = "MARIO_STATE";
1322 | packet.whoWon = -1; // For none
1323 | packet.x = mario.x;
1324 | packet.y = mario.y;
1325 | packet.vx = mario.vx;
1326 | packet.vy = mario.vy;
1327 | packet.ax = mario.ax;
1328 | packet.ay = mario.ay;
1329 | String motionState = "";
1330 | switch (mario.motionState) {
1331 | case STANDING: motionState = "STANDING";
1332 | break;
1333 | case RUNNING: motionState = "RUNNING";
1334 | break;
1335 | case JUMPING: motionState = "JUMPING";
1336 | break;
1337 | case FALLING: motionState = "FALLING";
1338 | break;
1339 | default: ;
1340 | break;
1341 | }
1342 | packet.motionState = motionState;
1343 | String faceState = "";
1344 | switch (mario.faceState) {
1345 | case FACE_FRONT: faceState = "FACE_FRONT";
1346 | break;
1347 | case FACE_LEFT: faceState = "FACE_LEFT";
1348 | break;
1349 | case FACE_RIGHT: faceState = "FACE_RIGHT";
1350 | break;
1351 | default: ;
1352 | break;
1353 | }
1354 | packet.faceState = faceState;
1355 | packet.boostJumping = mario.boostJumping;
1356 | packet.jumpPressed = mario.jumpPressed;
1357 | switch (mario.arrowX) {
1358 | case LEFT: packet.arrowX = "LEFT";
1359 | break;
1360 | case RIGHT: packet.arrowX = "RIGHT";
1361 | break;
1362 | default: packet.arrowX = "NONE";
1363 | break;
1364 | }
1365 | switch (mario.arrowY) {
1366 | case UP: packet.arrowY = "UP";
1367 | break;
1368 | case DOWN: packet.arrowY = "DOWN";
1369 | break;
1370 | default: packet.arrowY = "NONE";
1371 | break;
1372 | }
1373 | packet.frictionalForce = mario.frictionalForce;
1374 |
1375 | return packet;
1376 | }
1377 |
1378 | public Packet makeGameOverPacket(int whoWon) {
1379 | Packet packet = new Packet();
1380 | packet.type = "GAME_OVER";
1381 | packet.whoWon = whoWon;
1382 | return packet;
1383 | }
1384 |
1385 | public String packetToString(Packet packet) {
1386 | return gson.toJson(packet);
1387 | }
1388 |
1389 | public Packet stringToPacket(String packetStr) {
1390 | return gson.fromJson(packetStr, Packet.class);
1391 | }
1392 | }
1393 |
1394 |
1395 |
1396 |
1397 |
1398 | class UIManger {
1399 | public LinkedList inPackets;
1400 | public LinkedList outPackets;
1401 |
1402 | public UIManger() {
1403 | inPackets = new LinkedList();
1404 | outPackets = new LinkedList();
1405 | }
1406 |
1407 | public void update() {
1408 | // inPackets
1409 | for (int i = 0; i < inPackets.size(); ++i) {
1410 | inPackets.get(i).update();
1411 | }
1412 | // GC
1413 | while (true) {
1414 | if (inPackets.isEmpty()) break;
1415 |
1416 | TextSprite textSprite = inPackets.getFirst();
1417 | if (textSprite.y > HEIGHT) {
1418 | inPackets.removeFirst();
1419 | } else break;
1420 | }
1421 |
1422 | // outPackets
1423 | for (int i = 0; i < outPackets.size(); ++i) {
1424 | outPackets.get(i).update();
1425 | }
1426 | // GC
1427 | while (true) {
1428 | if (outPackets.isEmpty()) break;
1429 |
1430 | TextSprite textSprite = outPackets.getFirst();
1431 | if (textSprite.y < 0) {
1432 | outPackets.removeFirst();
1433 | } else break;
1434 | }
1435 | }
1436 |
1437 | public void display() {
1438 | if (! SHOW_PACKET_INDICATOR) return;
1439 |
1440 | // inPackets
1441 | for (int i = 0; i < inPackets.size(); ++i) {
1442 | inPackets.get(i).display();
1443 | }
1444 |
1445 | // outPackets
1446 | for (int i = 0; i < outPackets.size(); ++i) {
1447 | outPackets.get(i).display();
1448 | }
1449 | }
1450 |
1451 | public void addInPacket() {
1452 | inPackets.addLast(new TextSprite("@", 30, WIDTH, 0, 20));;
1453 | }
1454 |
1455 | public void addOutPacket() {
1456 | outPackets.addLast(new TextSprite("@", 30, 0, HEIGHT, -20));;
1457 | }
1458 | }
--------------------------------------------------------------------------------
/pde/MarioMultiplay/code/gson-2.8.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/gson-2.8.0.jar
--------------------------------------------------------------------------------
/pde/MarioMultiplay/code/jeromq-0.3.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/jeromq-0.3.5.jar
--------------------------------------------------------------------------------
/pde/MarioMultiplay/code/snakeyaml-1.17.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/snakeyaml-1.17.jar
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/Supermario_BGM_Overworld.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/Supermario_BGM_Overworld.mp3
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/config.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/config.yml
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/background_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/background_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/block_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/block_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/brick_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/brick_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_falling_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_falling_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_falling_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_falling_0001.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_jumping_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_jumping_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_running_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_running_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0001.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_running_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0002.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_running_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0003.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/bw_mario_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_standing_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/gold_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/gold_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0001.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/gold_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0002.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/gold_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0003.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/ground_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/ground_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/ground_filler_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/ground_filler_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_falling_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_falling_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_falling_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_falling_0001.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_jumping_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_jumping_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_running_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_running_0001.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0001.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_running_0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0002.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_running_0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0003.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/mario_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_standing_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/princess_standing_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/princess_standing_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/img/question_0000.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/question_0000.gif
--------------------------------------------------------------------------------
/pde/MarioMultiplay/data/map.txt:
--------------------------------------------------------------------------------
1 | BBBBBBBBBBBBBBBBBBBBBB
2 | BBBBBBBBBBBBBBBBBBBBBB
3 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB
4 | BBBBBBBBBBBBBBBBBBBBBB
5 | RRRRR RRRRRR BBBBBBBBBBBBBBBBBBBBBB
6 | BBBBBBBBBBBBBBBBBBBBBB
7 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB
8 | BBBBBBBBBBBBBBBBBBBBBB
9 | RRRR RRRRR OO RRR BBBBBBBBBBBBBBBBBBBBBB
10 | O O RRRBBBBBBBBBBBBBBBBBBBBBB
11 | BBBBBBBBBBBBBBBBBBBBBB
12 | B BBBBBBBBBBBBBBBBBBBBBB
13 | #####################################################################BBBBBBBBBBBBBBBBBBBBB
14 | #####################################################################BBBBBBBBBBBBBBBBBBBBB
15 |
--------------------------------------------------------------------------------
/server/Program.cs:
--------------------------------------------------------------------------------
1 | using NetMQ.Sockets;
2 | using System;
3 |
4 |
5 | namespace ConsoleApp1
6 | {
7 | public class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | using (var xsubSocket = new XSubscriberSocket("@tcp://*:1234"))
12 | using (var xpubSocket = new XPublisherSocket("@tcp://*:5678"))
13 | {
14 | Console.WriteLine("Intermediary started, and waiting for messages");
15 |
16 | // proxy messages between frontend / backend
17 | var proxy = new NetMQ.Proxy(xsubSocket, xpubSocket);
18 |
19 | // blocks indefinitely
20 | proxy.Start();
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/MarioMultiplay.java:
--------------------------------------------------------------------------------
1 | import processing.core.*;
2 | import ddf.minim.*;
3 | import java.util.Map;
4 | import java.util.Set;
5 | import java.util.HashSet;
6 | import java.util.ArrayList;
7 | import java.util.LinkedList;
8 | import java.lang.Math;
9 | import org.zeromq.ZMQ;
10 | import org.zeromq.ZMQ.Context;
11 | import org.zeromq.ZMQ.Socket;
12 | import com.google.gson.Gson;
13 | import java.util.concurrent.ConcurrentLinkedQueue;
14 | import org.yaml.snakeyaml.Yaml;
15 |
16 |
17 | public class MarioMultiplay extends PApplet {
18 |
19 | // In Eclipse, run this project as Java Application (not Applet)
20 | public static void main(String[] args) {
21 | String[] a = { "MarioMultiplay" };
22 | PApplet.runSketch(a, new MarioMultiplay());
23 | }
24 |
25 | public void settings() {
26 | size(WIDTH, HEIGHT, P2D);
27 | }
28 |
29 |
30 | // ============================= NEW FILE ==================================
31 |
32 |
33 | enum GAME_STATE {
34 | PLAYING,
35 | WIN,
36 | LOSE
37 | }
38 |
39 | // Global constants
40 |
41 | int FRAME_RATE = 30;
42 | int FRAMES_PER_SHOT = FRAME_RATE / 10;
43 | int WIDTH = 640;
44 | int HEIGHT = 432;
45 | int ASSET_SCALE = 2;
46 | float GRAVITY = 2.0f;
47 | float MARIO_JUMP_FORCE = 19.0f;
48 | float BOOST_JUMP_FORCE = 11.3f;
49 | float MARIO_VX_LIMIT = 9.0f;
50 | float MARIO_FORCE = 0.45f;
51 | int CAMERA_RANGE_LEFT = 170;
52 | int BLOCK_OFFSET = -16;
53 | String SERVER_HOST;
54 | String XSUB_PORT = "1234";
55 | String XPUB_PORT = "5678";
56 | int MY_MARIO_NUM;
57 | String ROOM_NUM;
58 | float MY_FRICTIONAL_FORCE = 0.4f; // 0.4 for normal world. 0.1 for ice world.
59 | boolean SHOW_PACKET_INDICATOR;
60 | boolean PACKET_FRUGAL = true;
61 |
62 | // Global variables
63 |
64 | GAME_STATE gameState = GAME_STATE.PLAYING;
65 | long frame = 0;
66 | Set pendingKeyboardEvents;
67 | boolean needSync = true;
68 |
69 | // Global objects
70 |
71 | Mario marioMe;
72 | Mario marioRival;
73 | Mario mario0;
74 | Mario mario1;
75 | Sprite bgImgSprite;
76 | Sprite princessSprite;
77 | BlockManager blockManager;
78 | CollisionDetector collisionDetector;
79 | EnemyCollisionDetector enemyCD;
80 | NetworkManager networkManager;
81 | UIManger uiManager;
82 | // Audio ------------- START
83 | AudioPlayer player;
84 | Minim minim; // Audio context
85 | // Audio ------------- END
86 | Gson gson;
87 |
88 | Context pubContext;
89 | Socket pubSocket;
90 | ConcurrentLinkedQueue msgQueue;
91 |
92 |
93 | public void setup() {
94 | background(51);
95 | frameRate(FRAME_RATE);
96 |
97 | // Config file
98 | Yaml yaml = new Yaml();
99 | String[] lines = loadStrings("config.yml");
100 | StringBuilder sb = new StringBuilder();
101 | for (String line: lines) sb.append(line).append("\n");
102 | String document = sb.toString();
103 | Map confMap = (Map) yaml.load(document);
104 | ROOM_NUM = (String) confMap.get("room_num");
105 | System.out.println("ROOM_NUM=" + ROOM_NUM);
106 | MY_MARIO_NUM = (int) confMap.get("my_mario_num");
107 | System.out.println("MY_MARIO_NUM=" + MY_MARIO_NUM);
108 | MY_FRICTIONAL_FORCE = (float) (double) confMap.get("my_frictional_force");
109 | System.out.println("MY_FRICTIONAL_FORCE=" + MY_FRICTIONAL_FORCE);
110 | SERVER_HOST = (String) confMap.get("server_host");
111 | System.out.println("SERVER_HOST=" + SERVER_HOST);
112 | SHOW_PACKET_INDICATOR = (boolean) confMap.get("show_packet_indicator");
113 | System.out.println("SHOW_PACKET_INDICATOR=" + SHOW_PACKET_INDICATOR);
114 | PACKET_FRUGAL = (boolean) confMap.get("packet_frugal");
115 | System.out.println("PACKET_FRUGAL=" + PACKET_FRUGAL);
116 |
117 | // Backgroud image
118 | bgImgSprite = new SharedSprite("img/background_", 1);
119 |
120 | // Princess
121 | princessSprite = new SharedSprite("img/princess_standing_", 1);
122 |
123 | // Block manager
124 | blockManager = new BlockManager();
125 |
126 | // Collision detector
127 | collisionDetector = new CollisionDetector();
128 | enemyCD = new EnemyCollisionDetector();
129 |
130 | // Well, our main character appears
131 | marioMe = new Mario(false); // Color
132 | marioRival = new Mario(true); // Black and White
133 |
134 | // marioMe, marioRival
135 | if (MY_MARIO_NUM == 0) {
136 | mario0 = marioMe;
137 | mario1 = marioRival;
138 | } else {
139 | mario1 = marioMe;
140 | mario0 = marioRival;
141 | }
142 | // Mario1 positioned at the right of mario0
143 | mario1.x = mario0.x + 300;
144 |
145 | // BGM
146 | minim = new Minim(this);
147 | player = minim.loadFile("Supermario_BGM_Overworld.mp3");
148 | player.play();
149 |
150 | // Network manager
151 | networkManager = new NetworkManager();
152 |
153 | // UI manager
154 | uiManager = new UIManger();
155 |
156 | // Gson
157 | gson = new Gson();
158 |
159 | // Publisher
160 | pubContext = ZMQ.context(1);
161 | pubSocket = pubContext.socket(ZMQ.PUB);
162 | pubSocket.connect("tcp://" + SERVER_HOST + ":" + XSUB_PORT);
163 |
164 | // Message queue
165 | msgQueue = new ConcurrentLinkedQueue();
166 |
167 | // Keyboard event queue
168 | pendingKeyboardEvents = new HashSet();
169 |
170 | // Subscriber thread mst be launched after msgQueue is initialized!
171 | new Thread(new Subscriber()).start();
172 | }
173 |
174 |
175 | public void stop() {
176 | // Audio player
177 | player.close();
178 | minim.stop();
179 |
180 | // ZeroMQ publisher
181 | pubSocket.close();
182 | pubContext.term();
183 |
184 | super.stop();
185 | }
186 |
187 |
188 | public void draw() {
189 | processUserInput();
190 |
191 | processGameLogic();
192 |
193 | doNetworkStuff();
194 |
195 | uiManager.update();
196 |
197 | render();
198 |
199 | ++frame;
200 | }
201 |
202 |
203 | // ============================= USER INPUT ================================
204 |
205 |
206 | public void processUserInput() {
207 | marioMe.processKeyboard();
208 | }
209 |
210 | /**
211 | * Called once every time a key is pressed. The key that was pressed is
212 | * stored in the key variable.
213 | */
214 | public void keyPressed() {
215 | marioMe.onKeyPress(key);
216 | }
217 |
218 | /**
219 | * Called once every time a key is released. The key that was released will
220 | * be stored in the 'key' member variable of PApplet.
221 | */
222 | public void keyReleased() {
223 | marioMe.onKeyRelease(key);
224 | }
225 |
226 |
227 | // ============================= GAME LOGIC ================================
228 |
229 |
230 | public void processGameLogic() {
231 | // Game over?
232 | if (gameState != GAME_STATE.PLAYING) return;
233 |
234 | // Update the shared sprites
235 | blockManager.spriteGold.update();
236 |
237 | // Do the simulation
238 | marioMe.update();
239 | marioRival.update();
240 | }
241 |
242 |
243 | // ======================== NETWORK COMMUNICATION ===========================
244 |
245 |
246 | public void doNetworkStuff() {
247 | networkWrite(); // First, send my mario's state, which is already set,
248 | networkRead(); // then set up ghost mario's state from what received.
249 | }
250 |
251 | public void networkRead() {
252 | // Game over?
253 | if (gameState != GAME_STATE.PLAYING) return;
254 |
255 | // Process all unread msgs
256 | ArrayList unreadMsgs = networkManager.getUnreadMsgs();
257 | for (String msg: unreadMsgs) {
258 | if (gameState == GAME_STATE.PLAYING) {
259 | processMsg(msg);
260 | uiManager.addInPacket();
261 | } else { // Game over
262 | ; // Throw packet
263 | }
264 | }
265 | }
266 |
267 | public void networkWrite() {
268 | // Game over?
269 | if (gameState != GAME_STATE.PLAYING) return;
270 |
271 | // Need sync?
272 | if (PACKET_FRUGAL && ! needSync) return;
273 |
274 | Packet packet = networkManager.makeMarioStatePacket(marioMe);
275 | String packetStr = networkManager.packetToString(packet);
276 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM;
277 | pubSocket.sendMore(channel);
278 | pubSocket.send(packetStr);
279 |
280 | // Notify UIManager
281 | uiManager.addOutPacket();
282 | }
283 |
284 | public void processMsg(String msg) {
285 | if (frame < 100) System.out.println("Frame " + frame + ": " + msg);
286 |
287 | Packet packet = networkManager.stringToPacket(msg);
288 |
289 | // Judge packet type
290 | switch (packet.type) {
291 | case "GAME_OVER":
292 | if (packet.whoWon == MY_MARIO_NUM) { // Win
293 | gameState = GAME_STATE.WIN;
294 | } else if (packet.whoWon == (1 - MY_MARIO_NUM)) { // Lose
295 | gameState = GAME_STATE.LOSE;
296 | }
297 | break;
298 | case "MARIO_STATE":
299 | // Setting mario state
300 | marioRival.x = packet.x;
301 | marioRival.y = packet.y;
302 | marioRival.vx = packet.vx;
303 | marioRival.vy = packet.vy;
304 | marioRival.ax = packet.ax;
305 | marioRival.ay = packet.ay;
306 | switch (packet.motionState) {
307 | case "STANDING": marioRival.motionState = MotionState.STANDING;
308 | break;
309 | case "RUNNING": marioRival.motionState = MotionState.RUNNING;
310 | break;
311 | case "JUMPING": marioRival.motionState = MotionState.JUMPING;
312 | break;
313 | case "FALLING": marioRival.motionState = MotionState.FALLING;
314 | break;
315 | default:
316 | break;
317 | }
318 | switch (packet.faceState) {
319 | case "FACE_FRONT": marioRival.faceState = MarioFace.FACE_FRONT;
320 | break;
321 | case "FACE_LEFT": marioRival.faceState = MarioFace.FACE_LEFT;
322 | break;
323 | case "FACE_RIGHT": marioRival.faceState = MarioFace.FACE_RIGHT;
324 | break;
325 | default:
326 | break;
327 | }
328 | marioRival.boostJumping = packet.boostJumping;
329 | marioRival.jumpPressed = packet.jumpPressed;
330 | switch (packet.arrowX) {
331 | case "LEFT": marioRival.arrowX = MarioArrowX.LEFT;
332 | break;
333 | case "RIGHT": marioRival.arrowX = MarioArrowX.RIGHT;
334 | break;
335 | default: marioRival.arrowX = MarioArrowX.NONE;
336 | break;
337 | }
338 | switch (packet.arrowY) {
339 | case "UP": marioRival.arrowY = MarioArrowY.UP;
340 | break;
341 | case "DOWN": marioRival.arrowY = MarioArrowY.DOWN;
342 | break;
343 | default: marioRival.arrowY = MarioArrowY.NONE;
344 | break;
345 | }
346 | marioRival.frictionalForce = packet.frictionalForce;
347 | break;
348 | default: ;
349 | break;
350 | }
351 | }
352 |
353 |
354 | public void gameOverWinning() {
355 | gameState = GAME_STATE.WIN;
356 |
357 | // Send GAME_OVER packet to the rival
358 | Packet packet = networkManager.makeGameOverPacket(MY_MARIO_NUM);
359 | String packetStr = networkManager.packetToString(packet);
360 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM;
361 | pubSocket.sendMore(channel);
362 | pubSocket.send(packetStr);
363 | }
364 |
365 | public void resetGame() {
366 | gameState = GAME_STATE.PLAYING;
367 | marioMe.x = 150 + MY_MARIO_NUM * 300;
368 | marioMe.y = 0;
369 | marioMe.vx = 0;
370 | marioMe.vy = 0;
371 | marioMe.ax = 0;
372 | marioMe.ay = GRAVITY;
373 | needSync = true;
374 | msgQueue.clear();
375 | }
376 |
377 |
378 | // ============================= RENDERING =================================
379 |
380 |
381 | public void render() {
382 | // Draw Background
383 | drawBackground();
384 |
385 | // Draw map
386 | drawMap();
387 |
388 | // Draw the Marios' sprite
389 | noFill();
390 | noStroke();
391 | marioRival.display();
392 | marioMe.display();
393 |
394 | // Draw UI
395 | uiManager.display();
396 |
397 | // Game Over?
398 | textSize(50);
399 | fill(255, 174, 201);
400 | textAlign(CENTER, CENTER);
401 | if (gameState == GAME_STATE.WIN) {
402 | String gameStateStr = "WIN";
403 | text(gameStateStr, WIDTH / 2, HEIGHT / 2);
404 | } else if (gameState == GAME_STATE.LOSE) {
405 | String gameStateStr = "LOSE";
406 | text(gameStateStr, WIDTH / 2, HEIGHT / 2);
407 | }
408 | }
409 |
410 | public void drawBackground() {
411 | fill(107, 140, 255);
412 | noStroke();
413 | rect(0, 0, WIDTH, HEIGHT);
414 | bgImgSprite.display(-1, 8);
415 | princessSprite.display(6639, 384 - princessSprite.getHeight());
416 | }
417 |
418 | public void drawMap() {
419 | blockManager.display();
420 | }
421 |
422 |
423 | // =============================== NEW FILE ==================================
424 |
425 |
426 | enum MotionState {
427 | STANDING, RUNNING, JUMPING, FALLING,
428 | }
429 |
430 | enum MarioFace {
431 | FACE_LEFT, FACE_RIGHT, FACE_FRONT
432 | }
433 |
434 | enum MarioArrowX {
435 | NONE,
436 | LEFT,
437 | RIGHT
438 | }
439 |
440 | enum MarioArrowY {
441 | NONE,
442 | UP,
443 | DOWN
444 | }
445 |
446 | class Mario {
447 | // Space state
448 | int width, height;
449 | float x = 150, y = 0;
450 | float vx = 0;
451 | float vy = 0;
452 | float ax = 0;
453 | float ay = GRAVITY;
454 |
455 | // Force state
456 | float frictionalForce = 0;
457 |
458 | // Motion state
459 | MotionState motionState = MotionState.JUMPING;
460 | MarioFace faceState = MarioFace.FACE_RIGHT;
461 |
462 | // Keyboard State
463 | MarioArrowX arrowX = MarioArrowX.NONE;
464 | MarioArrowY arrowY = MarioArrowY.NONE;
465 | boolean jumpPressed = false;
466 |
467 | boolean boostJumping = false;
468 |
469 | // Sprite state
470 | Sprite currentSprite = null;
471 |
472 | // Sprites
473 | Sprite marioStanding, marioRunning, marioJumping, marioFalling;
474 |
475 | Camera camera;
476 |
477 | boolean isGhost = false;
478 |
479 | Mario(boolean blackWhite) {
480 | // Load sprites
481 | if (! blackWhite) {
482 | marioStanding = new Sprite("img/mario_standing_", 1, true);
483 | marioRunning = new Sprite("img/mario_running_", 4, true);
484 | marioJumping = new Sprite("img/mario_jumping_", 1, true);
485 | marioFalling = new Sprite("img/mario_falling_", 2, true);
486 | } else {
487 | marioStanding = new Sprite("img/bw_mario_standing_", 1, true);
488 | marioRunning = new Sprite("img/bw_mario_running_", 4, true);
489 | marioJumping = new Sprite("img/bw_mario_jumping_", 1, true);
490 | marioFalling = new Sprite("img/bw_mario_falling_", 2, true);
491 |
492 | isGhost = true;
493 | }
494 |
495 | width = marioStanding.getWidth();
496 | height = marioStanding.getHeight();
497 |
498 | frictionalForce = MY_FRICTIONAL_FORCE;
499 |
500 | // Make an own camera
501 | camera = new Camera();
502 | }
503 |
504 | public void update() {
505 | _updateMotion();
506 | _updateSpeed();
507 | _updatePosition();
508 | }
509 |
510 | public void display() {
511 | // Select sprite
512 | if (motionState == MotionState.FALLING) currentSprite = marioFalling;
513 | else if (motionState == MotionState.JUMPING) currentSprite = marioJumping;
514 | else if (motionState == MotionState.RUNNING) currentSprite = marioRunning;
515 | else if (motionState == MotionState.STANDING) currentSprite = marioStanding;
516 |
517 | // Select face direction
518 | if (faceState == MarioFace.FACE_LEFT) {
519 | currentSprite.display(x, y, true);
520 | } else
521 | currentSprite.display(x, y);
522 | }
523 |
524 | public float getCenterX() {
525 | return x + 0.5f * width;
526 | }
527 |
528 | // Top two points of the hitbox
529 | public Coordinate[] getHitboxTop() {
530 | Coordinate vertx1 = new Coordinate(x + 2, y);
531 | Coordinate vertx2 = new Coordinate(x + width - 2, y);
532 | return new Coordinate[]{vertx1, vertx2};
533 | }
534 |
535 | // Bottom two points of the hitbox
536 | public Coordinate[] getHitboxBottom() {
537 | Coordinate vertx1 = new Coordinate(x + 2, y + height);
538 | Coordinate vertx2 = new Coordinate(x + width - 2, y + height);
539 | return new Coordinate[]{vertx1, vertx2};
540 | }
541 |
542 | public Hitbox getHitboxHead() {
543 | Hitbox hitbox = new Hitbox();
544 | hitbox.topLeft = new Coordinate(x + 2, y);
545 | hitbox.topRight = new Coordinate(x + width - 2, y);
546 | hitbox.bottomLeft = new Coordinate(x + 2, y + height - 25);
547 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height - 25);
548 | return hitbox;
549 | }
550 |
551 | public Hitbox getHitbox() {
552 | Hitbox hitbox = new Hitbox();
553 | hitbox.topLeft = new Coordinate(x + 2, y);
554 | hitbox.topRight = new Coordinate(x + width - 2, y);
555 | hitbox.bottomLeft = new Coordinate(x + 2, y + height);
556 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height);
557 | return hitbox;
558 | }
559 |
560 | public void onKeyPress(char key) {
561 | if (key == 'c' || key == 'C') {
562 | pendingKeyboardEvents.add("_jump");
563 | } else if (key == 'x' || key == '#') {
564 | ;
565 | } else if (key == ENTER) {
566 | if (gameState != GAME_STATE.PLAYING) resetGame();
567 | }else if (key == CODED) {
568 | if (keyCode == UP) {
569 | pendingKeyboardEvents.add("_up");
570 | } else if (keyCode == DOWN) {
571 | pendingKeyboardEvents.add("_down");
572 | }
573 | if (keyCode == LEFT) {
574 | pendingKeyboardEvents.add("_left");
575 | } else if (keyCode == RIGHT) {
576 | pendingKeyboardEvents.add("_right");
577 | }
578 | }
579 | }
580 |
581 | public void onKeyRelease(char key) {
582 | if (key == 'c' || key == 'C') {
583 | pendingKeyboardEvents.add("_release_jump");
584 | } else if (key == 'x' || key == '#') {
585 | ;
586 | } else if (key == CODED) {
587 | if (keyCode == UP) {
588 | pendingKeyboardEvents.add("_release_up");
589 | } else if (keyCode == DOWN) {
590 | pendingKeyboardEvents.add("_release_down");
591 | }
592 | if (keyCode == LEFT) {
593 | pendingKeyboardEvents.add("_release_left");
594 | } else if (keyCode == RIGHT) {
595 | pendingKeyboardEvents.add("_release_right");
596 | }
597 | }
598 | }
599 |
600 | public void processKeyboard() {
601 | if (pendingKeyboardEvents.isEmpty()) needSync = false;
602 | else needSync = true;
603 |
604 | for (String event: pendingKeyboardEvents) {
605 | switch (event) {
606 | case "_jump": _jump();
607 | break;
608 | case "_left": _left();
609 | break;
610 | case "_right": _right();
611 | break;
612 | case "_up": arrowY = MarioArrowY.UP;
613 | break;
614 | case "_down": arrowY = MarioArrowY.DOWN;
615 | break;
616 | case "_release_jump": jumpPressed = false;
617 | break;
618 | case "_release_left": if (arrowX == MarioArrowX.LEFT) arrowX = MarioArrowX.NONE;;
619 | break;
620 | case "_release_right": if (arrowX == MarioArrowX.RIGHT) arrowX = MarioArrowX.NONE;
621 | break;
622 | case "_release_up": if (arrowY == MarioArrowY.UP) arrowY = MarioArrowY.NONE;
623 | break;
624 | case "_release_down": if (arrowY == MarioArrowY.DOWN) arrowY = MarioArrowY.NONE;
625 | break;
626 | default: ;
627 | break;
628 | }
629 | }
630 | pendingKeyboardEvents.clear();
631 | }
632 |
633 | public void _jump() {
634 | if (motionState != MotionState.JUMPING) {
635 | motionState = MotionState.JUMPING;
636 | vy = -MARIO_JUMP_FORCE; // Bounce up! 위로 튕겨주다!
637 | }
638 | jumpPressed = true;
639 | }
640 |
641 | public void _boostJump() {
642 | if (! boostJumping) {
643 | boostJumping = true;
644 | vy -= BOOST_JUMP_FORCE;
645 | }
646 | }
647 |
648 | public void _disJump() {
649 | // Release the jumping state
650 | if (motionState == MotionState.JUMPING) motionState = MotionState.STANDING;
651 | boostJumping = false;
652 | vy = 0;
653 | }
654 |
655 | public void _friction() {
656 | // Damping to break
657 | if (vx > 0 && motionState != MotionState.JUMPING) ax = -frictionalForce;
658 | else if (vx < 0 && motionState != MotionState.JUMPING) ax = frictionalForce;
659 | }
660 |
661 | public void _left() {
662 | // Turn the face to left
663 | arrowX = MarioArrowX.LEFT;
664 |
665 | faceState = MarioFace.FACE_LEFT;
666 | // Run, if were standing. 서있던거면 달려라
667 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING;
668 |
669 | // Give a force to left
670 | if (vx > 0) vx = 0;
671 | ax = -MARIO_FORCE;
672 | }
673 |
674 | public void _right() {
675 | arrowX = MarioArrowX.RIGHT;
676 |
677 | // Turn the face to right
678 | faceState = MarioFace.FACE_RIGHT;
679 | // Run, if were standing. 서있던거면 달려라
680 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING;
681 |
682 | // Give a force to right
683 | if (vx < 0) vx = 0;
684 | ax = MARIO_FORCE;
685 | }
686 |
687 | public void _updateMotion() {
688 | if (arrowX == MarioArrowX.LEFT) {
689 | faceState = MarioFace.FACE_LEFT;
690 |
691 | if (motionState == MotionState.STANDING) { // 달리기 로직
692 | motionState = MotionState.RUNNING;
693 | }
694 | } else if (arrowX == MarioArrowX.RIGHT) {
695 | faceState = MarioFace.FACE_RIGHT;
696 | if (motionState == MotionState.STANDING) { // 달리기 로직
697 | motionState = MotionState.RUNNING;
698 | }
699 | } else if (arrowX == MarioArrowX.NONE) {
700 | // 占쏙옙占쌩깍옙 占쏙옙占쏙옙
701 | if (motionState == MotionState.RUNNING) motionState = MotionState.STANDING;
702 | _friction();
703 | }
704 | }
705 |
706 | public void _updateSpeed() {
707 | // Y-axis
708 | vy += ay;
709 | // Boost jump
710 | if (vy < 0 && vy > -4 && jumpPressed && ! boostJumping) _boostJump();
711 |
712 | // X-axis
713 | float previousVx = vx;
714 | vx += ax;
715 | // Limit speed
716 | if (Math.abs(vx) > MARIO_VX_LIMIT) {
717 | if (vx > 0) {
718 | vx = MARIO_VX_LIMIT;
719 | } else {
720 | vx = -MARIO_VX_LIMIT;
721 | }
722 | } else if (Math.abs(vx) < MARIO_FORCE) {
723 | vx = 0;
724 | ax = 0;
725 | } else if (previousVx * vx < 0) { // Symbol changed
726 | vx = 0;
727 | ax = 0;
728 | }
729 | }
730 |
731 | public void _updatePosition() {
732 | // Game Over?
733 | if (gameState != GAME_STATE.PLAYING) return;
734 |
735 | // Update X
736 | float previousX = x;
737 | x += vx;
738 | if (x < 0) x = 0;
739 | if (x > 7000) x = 7000;
740 |
741 | // Collision detection: X
742 | if (collisionDetector.left(this) || collisionDetector.right(this)){
743 | x = previousX;
744 | vx = 0;
745 | }
746 |
747 | // Update Y
748 | float previousY = y;
749 | y += vy;
750 |
751 | // Collision detection: Y
752 | if (collisionDetector.up(this)) {
753 | y = previousY;
754 | vy = 0;
755 | }
756 | if (y > HEIGHT - mario0.height || collisionDetector.down(this) ) {
757 | y = previousY;
758 | vy = 0;
759 | _disJump();
760 | }
761 |
762 | // Enemy Collision Detection
763 | if (! isGhost) { // FTS: Favor The Shooter. 고스트는 밟기 판정 안함
764 | if (enemyCD.isTrample(getHitboxBottom(), marioRival.getHitboxHead(), this)
765 | == HitType.ENEMY) {
766 | gameOverWinning();
767 | }
768 | }
769 |
770 | // Camera following
771 | if (getCenterX() - camera.getCenterX() > 0) { // Mario too right
772 | camera.x = getCenterX() - 0.5f * camera.width;
773 | } else if (camera.getCenterX() - getCenterX() > CAMERA_RANGE_LEFT) { // Mario too left
774 | camera.x = getCenterX() + CAMERA_RANGE_LEFT - 0.5f * camera.width;
775 | if (camera.x < 0) camera.x = 0;
776 | }
777 | }
778 | }
779 |
780 | // ============================== NEW FILE ==================================
781 |
782 | int framesPerShot = FRAMES_PER_SHOT;
783 |
784 | /**
785 | * Class for animating a sequence of GIFs
786 | */
787 | class Sprite {
788 | PImage[] images;
789 | PImage[] mirroredImages;
790 | int imageCount;
791 | int frame;
792 |
793 | Sprite(String imagePrefix, int count) {
794 | imageCount = count;
795 | images = new PImage[imageCount];
796 |
797 | for (int i = 0; i < imageCount; ++i) {
798 | // Use nf() to number format 'i' into four digits
799 | String filename = imagePrefix + nf(i, 4) + ".gif";
800 | PImage img = loadImage(filename);
801 | images[i] = _enlarge(img, 2);
802 | }
803 | }
804 |
805 | Sprite(String imagePrefix, int count, boolean hasMirrored) {
806 |
807 | imageCount = count;
808 | images = new PImage[imageCount];
809 |
810 | for (int i = 0; i < imageCount; ++i) {
811 | // Use nf() to number format 'i' into four digits
812 | String filename = imagePrefix + nf(i, 4) + ".gif";
813 | PImage img = loadImage(filename);
814 | images[i] = _enlarge(img, 2);
815 | }
816 |
817 | if (hasMirrored) {
818 | mirroredImages = new PImage[imageCount];
819 | for (int i = 0; i < imageCount; ++i) {
820 | mirroredImages[i] = _flip(images[i]);
821 | }
822 | }
823 | }
824 |
825 | public void display(float xpos, float ypos) { // 월드 좌표 받고
826 | frame = (frame + 1) % (imageCount * framesPerShot);
827 | int shotIndex = frame / framesPerShot;
828 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, images[shotIndex].width,
829 | images[shotIndex].height); // 카메라 좌표계로 그려줌
830 | }
831 |
832 | public void display(float xpos, float ypos, boolean reverse) {
833 | if (! reverse) display(xpos - marioMe.camera.x, ypos);
834 |
835 | frame = (frame + 1) % (imageCount * framesPerShot);
836 | int shotIndex = frame / framesPerShot;
837 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, mirroredImages[shotIndex].width,
838 | mirroredImages[shotIndex].height);
839 | }
840 |
841 | public int getWidth() {
842 | return images[0].width;
843 | }
844 |
845 | public int getHeight() {
846 | return images[0].height;
847 | }
848 |
849 | public PImage _enlarge(PImage image, int multiple) {
850 | PImage newImg;
851 | newImg = createImage(image.width * multiple, image.height * multiple, ARGB);
852 | for (int i = 0; i < image.width; ++i) {
853 | for (int j = 0; j < image.height; ++j) {
854 | newImg.set(2 * i, 2 * j, image.get(i, j));
855 | newImg.set(2 * i, 2 * j + 1, image.get(i, j));
856 | newImg.set(2 * i + 1, 2 * j, image.get(i, j));
857 | newImg.set(2 * i + 1, 2 * j + 1, image.get(i, j));
858 | }
859 | }
860 |
861 | return newImg;
862 | }
863 |
864 | public PImage _flip(PImage image) {
865 | PImage reverse;
866 | reverse = createImage(image.width, image.height, ARGB);
867 |
868 | for (int i = 0; i < image.width; ++i) {
869 | for (int j = 0; j < image.height; ++j) {
870 | int xPixel, yPixel;
871 | xPixel = image.width - 1 - i;
872 | yPixel = j;
873 | reverse.pixels[yPixel * image.width + xPixel] = image.pixels[j * image.width + i];
874 | }
875 | }
876 | return reverse;
877 | }
878 |
879 | }
880 |
881 | class SharedSprite extends Sprite {
882 |
883 | public SharedSprite(String imagePrefix, int count) {
884 | super(imagePrefix, count);
885 | }
886 |
887 | public SharedSprite(String imagePrefix, int count, boolean hasMirrored) {
888 | super(imagePrefix, count, hasMirrored);
889 | }
890 |
891 | public void display(float xpos, float ypos) {
892 | int shotIndex = frame / framesPerShot;
893 | image(images[shotIndex], xpos - marioMe.camera.x, ypos,
894 | images[shotIndex].width, images[shotIndex].height);
895 | }
896 |
897 | public void display(float xpos, float ypos, boolean reverse) {
898 | int shotIndex = frame / framesPerShot;
899 |
900 | if (! reverse) {
901 | image(images[shotIndex], xpos - marioMe.camera.x, ypos,
902 | images[shotIndex].width, images[shotIndex].height);
903 | } else {
904 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos,
905 | mirroredImages[shotIndex].width, mirroredImages[shotIndex].height);
906 | }
907 | }
908 |
909 | public void update() {
910 | frame = (frame + 1) % (imageCount * framesPerShot);
911 | }
912 | }
913 |
914 | class TextSprite {
915 | private float x, y;
916 | private float vy;
917 | private String _text;
918 | private int size;
919 | int r;
920 | int g;
921 | int b;
922 |
923 | public TextSprite(String text, int size, float x, float y, float vy) {
924 | this.x = x;
925 | this.y = y;
926 | this._text = text;
927 | this.size = size;
928 | this.vy = vy;
929 |
930 | this.r = (int) random(255);
931 | this.g = (int) random(255);
932 | this.b = (int) random(255);
933 | }
934 |
935 | public void update() {
936 | this.y += vy;
937 | }
938 |
939 | public void display() {
940 | textSize(size);
941 | fill(0, 0, 0);
942 | text(_text, x + 1, y + 1);
943 | if (PACKET_FRUGAL) fill(255, 255, 255);
944 | else fill(r, g, b);
945 | textAlign(CENTER, CENTER);
946 | text(_text, x, y);
947 | }
948 | }
949 |
950 |
951 | // =========================== NEW FILE ==================================
952 |
953 |
954 | class Camera {
955 | int width, height; // Camera sight
956 | float x = 0, y = 0; // Camera's absolute coordinate
957 |
958 | Camera() {
959 | width = WIDTH;
960 | height = HEIGHT;
961 | }
962 |
963 | float getCenterX() {
964 | return x + 0.5f * width;
965 | }
966 | }
967 |
968 |
969 | // =========================== NEW FILE ==================================
970 |
971 |
972 | class Block {
973 | int x = 0, y = 0;
974 | int xB = 0, yB = 0;
975 | char blockType;
976 | boolean visible = true;
977 | SharedSprite sprite;
978 |
979 | public Block(int xB, int yB, char blockType) {
980 | this.xB = xB;
981 | this.yB = yB;
982 | this.x = 16 * ASSET_SCALE * xB + BLOCK_OFFSET;
983 | this.y = 16 * ASSET_SCALE * yB;
984 |
985 | this.visible = true;
986 | this.blockType = blockType;
987 | }
988 |
989 | public void display() {
990 | if (sprite != null) {
991 | sprite.display(x, y); // 월드 좌표 받음
992 | }
993 | }
994 | }
995 |
996 | class Coordinate {
997 | float x = 0, y = 0;
998 |
999 | public Coordinate(float x, float y) {
1000 | this.x = x;
1001 | this.y = y;
1002 | }
1003 | }
1004 |
1005 | class BlockManager {
1006 | SharedSprite spriteGround;
1007 | SharedSprite spriteBrick;
1008 | SharedSprite spriteBlock;
1009 | SharedSprite spriteQuestion;
1010 | SharedSprite spriteGold;
1011 | ArrayList> blocks = new ArrayList>();
1012 |
1013 | public BlockManager() {
1014 | // Load sprites
1015 | spriteGround = new SharedSprite("img/ground_", 1);
1016 | spriteBlock = new SharedSprite("img/block_", 1);
1017 | spriteBrick = new SharedSprite("img/brick_", 1);
1018 | spriteQuestion = new SharedSprite("img/question_", 1);
1019 | spriteGold = new SharedSprite("img/gold_", 4);
1020 |
1021 | // Read map.txt
1022 | String[] lines = loadStrings("map.txt");
1023 | // Make blocks
1024 | println("Start to make blocks..");
1025 | for (int i = 0; i < lines.length; ++i) {
1026 | String line = lines[i];
1027 | char[] charArray = line.toCharArray();
1028 | ArrayList blockRow = new ArrayList();
1029 | for (int j = 0; j < charArray.length; ++j) {
1030 | char c = charArray[j];
1031 | Block block = new Block(j, i, c);
1032 | switch (c) {
1033 | case ' ' :
1034 | break;
1035 | case '#' : ; block.sprite = spriteGround; // Ground
1036 | break;
1037 | case 'B' : block.sprite = spriteBlock; // Block
1038 | break;
1039 | case 'R' : block.sprite = spriteBrick; // Block
1040 | break;
1041 | case '?' : block.sprite = spriteQuestion; // Question mark
1042 | break;
1043 | case 'O' : block.sprite = spriteGold; // Gold
1044 | break;
1045 | default :
1046 | break;
1047 | }
1048 |
1049 | blockRow.add(block);
1050 | print(c);
1051 | }
1052 | blocks.add(blockRow);
1053 | println();
1054 | }
1055 | }
1056 |
1057 | public void display() {
1058 | int xStart;
1059 | if ((x2bX(marioMe.camera.getCenterX()) - 11) < 0) xStart = 0;
1060 | else xStart = x2bX(marioMe.camera.getCenterX()) - 11;
1061 | int xEnd = xStart + 23;
1062 |
1063 | for (int i = 0; i < blocks.size(); ++i) {
1064 | for (int j = xStart; j < Math.min(xEnd + 1, blocks.get(i).size()); ++j) {
1065 | Block block = blocks.get(i).get(j);
1066 | block.display();
1067 | }
1068 | }
1069 | }
1070 |
1071 | public Coordinate block2Coor(int bX, int bY) {
1072 | Coordinate coordinate = new Coordinate(16 * ASSET_SCALE * bX,
1073 | 16 * ASSET_SCALE * bY);
1074 | return coordinate;
1075 | }
1076 |
1077 | public int y2bY(float y) {
1078 | return (int) y / (16 * ASSET_SCALE);
1079 | }
1080 |
1081 | public int x2bX(float x) {
1082 | return (int) (x - BLOCK_OFFSET) / (16 * ASSET_SCALE);
1083 | }
1084 | }
1085 |
1086 |
1087 | // ============================ NEW FILE ==================================
1088 |
1089 |
1090 | class CollisionDetector {
1091 | public boolean up(Mario mario) {
1092 | if ((int)mario.y <= 0) return false;
1093 |
1094 | int step = 24;
1095 | for (int i = 0; i < 2; ++i) {
1096 | float x = mario.x + i * step;
1097 | int bX = blockManager.x2bX(x);
1098 | int bY = blockManager.y2bY(mario.y);
1099 |
1100 | ArrayList blockRow = blockManager.blocks.get(bY);
1101 | if (blockRow.size() <= bX) continue; // No block
1102 |
1103 | Block block = blockRow.get(bX);
1104 | if (block == null) continue;
1105 | if (block.blockType == ' ' || block.blockType == 'O')
1106 | continue;
1107 | else return true;
1108 | }
1109 |
1110 | // 위에서 true로 미리 빠져나가지 않았다
1111 | return false;
1112 | }
1113 |
1114 | public boolean down(Mario mario) {
1115 | if ((int)mario.y <= 0) return false;
1116 |
1117 | int step = 24;
1118 | for (int i = 0; i < 2; ++i) {
1119 | float x = mario.x + i * step;
1120 | int bX = blockManager.x2bX(x);
1121 | int bY = blockManager.y2bY(mario.y + mario.height);
1122 |
1123 | ArrayList blockRow = blockManager.blocks.get(bY);
1124 | if (blockRow.size() <= bX) continue; // No block
1125 |
1126 | Block block = blockRow.get(bX);
1127 | if (block == null) continue;
1128 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1129 | else return true;
1130 | }
1131 |
1132 | // 위에서 true로 미리 빠져나가지 않았다
1133 | return false;
1134 | }
1135 |
1136 | public boolean left(Mario mario) {
1137 | if ((int)mario.y <= 0) return false;
1138 |
1139 | int step = mario.height / 2;
1140 | for (int i = 0; i < 3; ++i) {
1141 | float y = mario.y + i * step;
1142 | int bX = blockManager.x2bX(mario.x);
1143 | int bY = blockManager.y2bY(y);
1144 |
1145 | ArrayList blockRow = blockManager.blocks.get(bY);
1146 | if (blockRow.size() <= bX) continue; // No block
1147 |
1148 | Block block = blockRow.get(bX);
1149 | if (block == null) continue;
1150 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1151 | else return true;
1152 | }
1153 |
1154 | // 위에서 true로 미리 빠져나가지 않았다
1155 | return false;
1156 | }
1157 |
1158 | public boolean right(Mario mario) {
1159 | if ((int)mario.y <= 0) return false;
1160 |
1161 | int step = mario.height / 2;
1162 | for (int i = 0; i < 3; ++i) {
1163 | float y = mario.y + i * step;
1164 | int bX = blockManager.x2bX(mario.x + mario.width);
1165 | int bY = blockManager.y2bY(y);
1166 |
1167 | ArrayList blockRow = blockManager.blocks.get(bY);
1168 | if (blockRow.size() <= bX) continue; // No block
1169 |
1170 | Block block = blockRow.get(bX);
1171 | if (block == null) continue;
1172 | if (block.blockType == ' ' || block.blockType == 'O') continue;
1173 | else return true;
1174 | }
1175 |
1176 | // 위에서 true로 미리 빠져나가지 않았다
1177 | return false;
1178 | }
1179 | }
1180 |
1181 |
1182 | // ============================= NEW FILE ==================================
1183 |
1184 |
1185 | class Rect {
1186 | Coordinate topLeft;
1187 | Coordinate topRight;
1188 | Coordinate bottomLeft;
1189 | Coordinate bottomRight;
1190 | }
1191 |
1192 | class Hitbox extends Rect {}
1193 |
1194 | enum HitType {
1195 | NONE,
1196 | ENEMY
1197 | }
1198 |
1199 | class EnemyCollisionDetector {
1200 | public HitType isTrample(Coordinate[] myHitboxBottom, Hitbox enemyHitbox,
1201 | Mario marioMe) {
1202 | if (marioMe.vy < 0.1) return HitType.NONE;
1203 |
1204 | Coordinate[] hbVertexes = new Coordinate[]{
1205 | enemyHitbox.topLeft,
1206 | enemyHitbox.topRight,
1207 | enemyHitbox.bottomLeft,
1208 | enemyHitbox.bottomRight
1209 | };
1210 | for (Coordinate foot: myHitboxBottom) {
1211 | boolean isMinX = true;
1212 | for (Coordinate vertx: hbVertexes) {
1213 | if (foot.x >= vertx.x) {
1214 | isMinX = false;
1215 | break;
1216 | }
1217 | }
1218 | if (isMinX) continue;
1219 |
1220 | boolean isMaxX = true;
1221 | for (Coordinate vertx: hbVertexes) {
1222 | if (foot.x <= vertx.x) {
1223 | isMaxX = false;
1224 | break;
1225 | }
1226 | }
1227 | if (isMaxX) continue;
1228 |
1229 | boolean isMinY = true;
1230 | for (Coordinate vertx: hbVertexes) {
1231 | if (foot.y >= vertx.y) {
1232 | isMinY = false;
1233 | break;
1234 | }
1235 | }
1236 | if (isMinY) continue;
1237 |
1238 | boolean isMaxY = true;
1239 | for (Coordinate vertx: hbVertexes) {
1240 | if (foot.y <= vertx.y) {
1241 | isMaxY = false;
1242 | break;
1243 | }
1244 | }
1245 | if (isMaxY) continue;
1246 |
1247 | // Neither min nor max, return HIT
1248 | return HitType.ENEMY;
1249 | }
1250 |
1251 | // Not hit
1252 | return HitType.NONE;
1253 | }
1254 | }
1255 |
1256 |
1257 | // ============================= NEW FILE ==================================
1258 |
1259 |
1260 | class Subscriber implements Runnable {
1261 | Context context;
1262 | Socket subSocket;
1263 |
1264 | // Constructor
1265 | public Subscriber () {
1266 | context = ZMQ.context(1);
1267 | subSocket = context.socket(ZMQ.SUB);
1268 | subSocket.connect("tcp://" + SERVER_HOST + ":" + XPUB_PORT);
1269 | String channel = (ROOM_NUM + "CHANNEL_MARIO_" + String.valueOf(1 - MY_MARIO_NUM));
1270 | // String channel = (ROOM_NUM + "CHANNEL_MARIO_0");
1271 |
1272 | subSocket.subscribe(channel.getBytes(ZMQ.CHARSET));
1273 | }
1274 |
1275 | public void run() {
1276 | while (!Thread.currentThread().isInterrupted()) {
1277 | /* It read synchronously, so no need to sleep().
1278 | * It is copied from the official sample code.
1279 | */
1280 |
1281 | // Read envelope with address
1282 | String channel = subSocket.recvStr();
1283 | // Read message contents
1284 | String contents = subSocket.recvStr();
1285 | //System.out.println("Received from: " + channel + " Contents: " + contents);
1286 | msgQueue.add(contents);
1287 | }
1288 | }
1289 |
1290 | public void finallize() throws Throwable {
1291 | try{
1292 | if (subSocket != null) subSocket.close();
1293 | if (context != null) context.term();
1294 | }
1295 | finally {
1296 | super.finalize();
1297 | }
1298 | }
1299 | }
1300 |
1301 |
1302 | // ============================= NEW FILE ==================================
1303 |
1304 |
1305 | class Packet {
1306 | public String type; // "MARIO_STATE" / "GAME_OVER"
1307 | public int whoWon; // 0, 1, -1
1308 | public float x;
1309 | public float y;
1310 | public float vx;
1311 | public float vy;
1312 | public float ax;
1313 | public float ay;
1314 | public String motionState;
1315 | public String faceState;
1316 | public boolean boostJumping;
1317 | public boolean jumpPressed;
1318 | public String arrowX;
1319 | public String arrowY;
1320 | public float frictionalForce;
1321 | }
1322 |
1323 |
1324 | // ============================= NEW FILE ==================================
1325 |
1326 |
1327 | class NetworkManager {
1328 | public ArrayList getUnreadMsgs() {
1329 | ArrayList unreadMsgs = new ArrayList();
1330 | String msg;
1331 | while ((msg = msgQueue.poll()) != null) {
1332 | unreadMsgs.add(msg);
1333 | }
1334 | return unreadMsgs;
1335 | }
1336 |
1337 | public Packet makeMarioStatePacket(Mario mario) {
1338 | Packet packet = new Packet();
1339 | packet.type = "MARIO_STATE";
1340 | packet.whoWon = -1; // For none
1341 | packet.x = mario.x;
1342 | packet.y = mario.y;
1343 | packet.vx = mario.vx;
1344 | packet.vy = mario.vy;
1345 | packet.ax = mario.ax;
1346 | packet.ay = mario.ay;
1347 | String motionState = "";
1348 | switch (mario.motionState) {
1349 | case STANDING: motionState = "STANDING";
1350 | break;
1351 | case RUNNING: motionState = "RUNNING";
1352 | break;
1353 | case JUMPING: motionState = "JUMPING";
1354 | break;
1355 | case FALLING: motionState = "FALLING";
1356 | break;
1357 | default: ;
1358 | break;
1359 | }
1360 | packet.motionState = motionState;
1361 | String faceState = "";
1362 | switch (mario.faceState) {
1363 | case FACE_FRONT: faceState = "FACE_FRONT";
1364 | break;
1365 | case FACE_LEFT: faceState = "FACE_LEFT";
1366 | break;
1367 | case FACE_RIGHT: faceState = "FACE_RIGHT";
1368 | break;
1369 | default: ;
1370 | break;
1371 | }
1372 | packet.faceState = faceState;
1373 | packet.boostJumping = mario.boostJumping;
1374 | packet.jumpPressed = mario.jumpPressed;
1375 | switch (mario.arrowX) {
1376 | case LEFT: packet.arrowX = "LEFT";
1377 | break;
1378 | case RIGHT: packet.arrowX = "RIGHT";
1379 | break;
1380 | default: packet.arrowX = "NONE";
1381 | break;
1382 | }
1383 | switch (mario.arrowY) {
1384 | case UP: packet.arrowY = "UP";
1385 | break;
1386 | case DOWN: packet.arrowY = "DOWN";
1387 | break;
1388 | default: packet.arrowY = "NONE";
1389 | break;
1390 | }
1391 | packet.frictionalForce = mario.frictionalForce;
1392 |
1393 | return packet;
1394 | }
1395 |
1396 | public Packet makeGameOverPacket(int whoWon) {
1397 | Packet packet = new Packet();
1398 | packet.type = "GAME_OVER";
1399 | packet.whoWon = whoWon;
1400 | return packet;
1401 | }
1402 |
1403 | public String packetToString(Packet packet) {
1404 | return gson.toJson(packet);
1405 | }
1406 |
1407 | public Packet stringToPacket(String packetStr) {
1408 | return gson.fromJson(packetStr, Packet.class);
1409 | }
1410 | }
1411 |
1412 |
1413 | // ============================= NEW FILE ==================================
1414 |
1415 |
1416 | class UIManger {
1417 | public LinkedList inPackets;
1418 | public LinkedList outPackets;
1419 |
1420 | public UIManger() {
1421 | inPackets = new LinkedList();
1422 | outPackets = new LinkedList();
1423 | }
1424 |
1425 | public void update() {
1426 | // inPackets
1427 | for (int i = 0; i < inPackets.size(); ++i) {
1428 | inPackets.get(i).update();
1429 | }
1430 | // GC
1431 | while (true) {
1432 | if (inPackets.isEmpty()) break;
1433 |
1434 | TextSprite textSprite = inPackets.getFirst();
1435 | if (textSprite.y > HEIGHT) {
1436 | inPackets.removeFirst();
1437 | } else break;
1438 | }
1439 |
1440 | // outPackets
1441 | for (int i = 0; i < outPackets.size(); ++i) {
1442 | outPackets.get(i).update();
1443 | }
1444 | // GC
1445 | while (true) {
1446 | if (outPackets.isEmpty()) break;
1447 |
1448 | TextSprite textSprite = outPackets.getFirst();
1449 | if (textSprite.y < 0) {
1450 | outPackets.removeFirst();
1451 | } else break;
1452 | }
1453 | }
1454 |
1455 | public void display() {
1456 | if (! SHOW_PACKET_INDICATOR) return;
1457 |
1458 | // inPackets
1459 | for (int i = 0; i < inPackets.size(); ++i) {
1460 | inPackets.get(i).display();
1461 | }
1462 |
1463 | // outPackets
1464 | for (int i = 0; i < outPackets.size(); ++i) {
1465 | outPackets.get(i).display();
1466 | }
1467 | }
1468 |
1469 | public void addInPacket() {
1470 | inPackets.addLast(new TextSprite("@", 30, WIDTH, 0, 20));;
1471 | }
1472 |
1473 | public void addOutPacket() {
1474 | outPackets.addLast(new TextSprite("@", 30, 0, HEIGHT, -20));;
1475 | }
1476 | }
1477 | }
--------------------------------------------------------------------------------