About Monkey 2 › Forums › Monkey 2 Code Library › Simple cheat code class (My first attempt at using Monkey2!)
Tagged: cheat code
This topic contains 7 replies, has 2 voices, and was last updated by
jondecker76
1 year, 2 months ago.
-
AuthorPosts
-
February 10, 2018 at 6:23 am #13597
I wanted to get a small start playing with Monkey2, so I thought I would build something simple but fun. I made a quick class to implement simple sequential cheat codes, and whipped up a quick demo of the old NES Contra cheat (Up, Up, Down, Down, Left, Right, Left, Right, B, A, B, A, Enter)
I’m still learning all of these new features, so please chime in if you see something that could have been done more efficiently.
Issues:
No real issues, only keyboard is supported right now. I would like to get away from using the event system for checking keyboard input, but I haven’t found an elegant way to check that A) a key was pressed and B) which key it was – so for now until I figure it out, the OnKeyEvent will have to do..Plans:
I do plan on making this a full-fledged module that supports Keyboard, Joystick, Mouse Buttons and even other sequences as individual strokes (so that sequences can be chained or share a common base)Anyways, give it a try and see what you think
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147Namespace CheatSequence#Import "<std>"#Import "<mojo>"Using std..Using mojo..' ------------------------------------------------------------------------------------------------------------------' SIMPLE CHEAT CLASS' ------------------------------------------------------------------------------------------------------------------' We will define some constantsEnum Strokekey = 1joy = 2EndClass CheatSequencePublicField numStrokes:Int ' Number of strokes in this sequenceField strokeCounter:Int ' Current stroke in the sequenceField strokes:Map<Int,SequenceStroke> ' Map of strokes in the sequenceField timeOut:Int ' millisecs timeoutField lastStrokeTimestamp:Int ' timestamp of last stroke input' Create a new cheat sequence' Default to a 1 second timeoutMethod New(thisTimeOut:Int=1000)numStrokes=0strokeCounter=0timeOut=thisTimeOutstrokes=New Map<Int,SequenceStroke>End' Add a stroke to the sequenceMethod AddStroke(strokeType:Int,strokeValue:Int)strokes.Add(numStrokes,New SequenceStroke(strokeType,strokeValue))numStrokes+= 1End Method' The update method should be called any time a key is pressed' The OnKeyEvent is recommended' TODO: Add joystick and mouse supportMethod Update(thisKey:Int)If strokes.Get(strokeCounter).strokeType=Stroke.key And strokes.Get(strokeCounter).strokeValue=thisKeySelf.strokeCounter+=1Self.lastStrokeTimestamp=Millisecs()ElseSelf.strokeCounter=0EndifEnd Method' Check to see if the cheat is successful' This should be called each iteration through the game loopMethod Success:Bool()' We can't be successful if we timed out, lets handle that firstIf Millisecs()-lastStrokeTimestamp > timeOut And strokeCounter>0'We timed out, lets restartSelf.strokeCounter=0lastStrokeTimestamp=Millisecs()Else'If we've made it this far, lets see if we got it!If (Self.strokeCounter=Self.numStrokes)Self.strokeCounter=0Return TrueEndifEndifReturn FalseEndEnd' Store a cheat stroke. This includes the kind of stroke (keyboard, joystick, etc..)' the value of the stroke (whick key or button)Class SequenceStrokeField strokeType:IntField strokeValue:IntMethod New(thisStrokeType:Int,thisStrokeValue:Int)strokeType=thisStrokeTypestrokeValue=thisStrokeValueEndEnd' ------------------------------------------------------------------------------------------------------------------' END SIMPLE CHEAT CLASS' ------------------------------------------------------------------------------------------------------------------Class MyWindow Extends WindowField contraCheat:CheatSequenceField lives:IntMethod New()Super.New( "Cheat Code Test",640,480,WindowFlags.Fullscreen )lives=3contraCheat= New CheatSequence(1000)contraCheat.AddStroke(Stroke.key,Key.Up)contraCheat.AddStroke(Stroke.key,Key.Up)contraCheat.AddStroke(Stroke.key,Key.Down)contraCheat.AddStroke(Stroke.key,Key.Down)contraCheat.AddStroke(Stroke.key,Key.Left)contraCheat.AddStroke(Stroke.key,Key.Right)contraCheat.AddStroke(Stroke.key,Key.Left)contraCheat.AddStroke(Stroke.key,Key.Right)contraCheat.AddStroke(Stroke.key,Key.B)contraCheat.AddStroke(Stroke.key,Key.A)contraCheat.AddStroke(Stroke.key,Key.B)contraCheat.AddStroke(Stroke.key,Key.A)contraCheat.AddStroke(Stroke.key,Key.Enter)EndMethod OnRender( canvas:Canvas ) OverrideIf contraCheat.Success() ' Check to see if our cheat was successfullives+=30EndifLocal delta:=Millisecs() - contraCheat.lastStrokeTimestampcanvas.Color=Color.Goldcanvas.DrawText("Lives: " + lives,App.ActiveWindow.Width/2 ,App.ActiveWindow.Height/2)canvas.Color=Color.Whitecanvas.DrawText("Try the cheat! UUDDLRLRBABA<Enter>",4,App.ActiveWindow.Height-95)canvas.DrawText("Timer: " + delta,4,App.ActiveWindow.Height-55)canvas.DrawText("numStokes: " + contraCheat.numStrokes,4,App.ActiveWindow.Height-40)canvas.DrawText("strokeCounter : " + contraCheat.strokeCounter,4,App.ActiveWindow.Height-25)App.RequestRender()EndMethod OnKeyEvent( event:KeyEvent ) Overrideselect event.TypeCase EventType.KeyDownIf ( event.Key=Key.Escape ) Then App.Terminate()contraCheat.Update(event.Key) ' Update the cheat sequence any time a key is pressedEndEndEndFunction Main()New AppInstanceNew MyWindowApp.Run()EndFebruary 10, 2018 at 11:07 am #13599A few notes:
- There is App.KeyEventFilter event you can subscribing to instead of OnKeyEvent – it allow us grab events outside of View class hierarchy
- You can also provide method AddStrokes( strokeType:Int,strokeValues:Int[] ) so that we can add sequences simplier way passing array.
February 10, 2018 at 6:14 pm #13606Thanks for the tips nerobot
I implemented the AddStrokes method, and you’re right – it’s much cleaner for usage
I’m lost on the App.KeyEventFilter though… There is no real documentation, and looking at the source has me scratching my head. It’s a field, but has a void return type?? And parameters? Wouldn’t that make it a method instead of a field? So confusing! I also looked at event.monkey2 and was left equally confused. There’s so much I have to learn about Monkey 2
February 11, 2018 at 4:58 am #13607I’m lost on the App.KeyEventFilter though… There is no real documentation, and looking at the source has me scratching my head. It’s a field, but has a void return type?? And parameters? Wouldn’t that make it a method instead of a field? So confusing!
It is called “First class functions”, it’s a reference to a function/method stored in a variable.
It uses for signals, like KeyEventFilter, and you can to subscribe to it via += operator.
Monkey1App.KeyEventFilter += YourMethodWithTheSameSignatureAsKeyEventFilteror
Monkey12345App.KeyEventFilter += Lambda() ' lambda also with the same signature' you code hereEndAnd there is my variant of your class (with comments):
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687Class CheatSequenceField OnSuccess:Void() 'signal for completing this sequenceField numStrokes:Int ' Number of strokes in this sequenceField strokeCounter:Int ' Current stroke in the sequenceField strokes:Map<Int,SequenceStroke> ' Map of strokes in the sequenceField timeOut:Int ' millisecs timeoutField lastStrokeTimestamp:Int ' timestamp of last stroke input' Create a new cheat sequence' Default to a 1 second timeoutMethod New(thisTimeOut:Int=1000)numStrokes=0strokeCounter=0timeOut=thisTimeOutstrokes=New Map<Int,SequenceStroke>Enabled=True ' enable by defaultEnd' Add a stroke to the sequenceMethod AddStroke(strokeType:Int,strokeValue:Int)strokes.Add(numStrokes,New SequenceStroke(strokeType,strokeValue))numStrokes+= 1End MethodProperty Enabled:Bool()Return _enabledSetter( value:Bool )If value=_enabled Return_enabled=valueIf _enabledApp.KeyEventFilter+=KeyFilter ' subscribe to filterElseApp.KeyEventFilter-=KeyFilter ' unsubscribe from filterEndifEndPrivateField _enabled:Bool' our key filtering functionMethod KeyFilter( event:KeyEvent )' just pass event key into updateUpdate( event.Key )' and check does a user finish the sequenceLocal ok:=Success()If okOnSuccess() ' and emit event if successEndifEnd' The update method should be called any time a key is pressed' The OnKeyEvent is recommended' TODO: Add joystick and mouse supportMethod Update(thisKey:Int)If strokes.Get(strokeCounter).strokeType=Stroke.key And strokes.Get(strokeCounter).strokeValue=thisKeySelf.strokeCounter+=1Self.lastStrokeTimestamp=Millisecs()ElseSelf.strokeCounter=0EndifEnd Method' Check to see if the cheat is successful' This should be called each iteration through the game loopMethod Success:Bool()' We can't be successful if we timed out, lets handle that firstIf Millisecs()-lastStrokeTimestamp > timeOut And strokeCounter>0'We timed out, lets restartSelf.strokeCounter=0lastStrokeTimestamp=Millisecs()Else'If we've made it this far, lets see if we got it!If (Self.strokeCounter=Self.numStrokes)Self.strokeCounter=0Return TrueEndifEndifReturn FalseEndEndAs you can see I added OnSuccess signal that will be emitted right after user press key that finished the sequence.
To use this signal you should to subscribe to it, somewhere you create the sequence – inside of MyWindow::New() in your case:
Monkey1234567891011121314151617181920212223242526Method New()Super.New( "Cheat Code Test",640,480,WindowFlags.Fullscreen )lives=3contraCheat= New CheatSequence(1000)contraCheat.AddStroke(Stroke.key,Key.Up)contraCheat.AddStroke(Stroke.key,Key.Up)contraCheat.AddStroke(Stroke.key,Key.Down)contraCheat.AddStroke(Stroke.key,Key.Down)contraCheat.AddStroke(Stroke.key,Key.Left)contraCheat.AddStroke(Stroke.key,Key.Right)contraCheat.AddStroke(Stroke.key,Key.Left)contraCheat.AddStroke(Stroke.key,Key.Right)contraCheat.AddStroke(Stroke.key,Key.B)contraCheat.AddStroke(Stroke.key,Key.A)contraCheat.AddStroke(Stroke.key,Key.B)contraCheat.AddStroke(Stroke.key,Key.A)contraCheat.AddStroke(Stroke.key,Key.Enter)' subscribecontraCheat.OnSuccess+=Lambda()lives+=30EndEndAnd you should remove all usages of Succes and Update methods of CheatSequence from MyWindow code – they are private now!
So, now CheatSequence became more independant of Window class or any other – you just create sequence and fill it with keys and subscribe to its OnSucces.
February 11, 2018 at 6:07 am #13608Thank you!
I just learned more from this single exercise than I have in the last week reading everything I could. Very clean and elegant, now it’s making a lot more sense!
February 11, 2018 at 6:01 pm #13613I’ve been improving on this today and playing with it more. There’s only one problem so far with using the key event filter.
The App.KeyEventFilter isn’t a one-shot, so each press of the key registers 2-3 hits even on quick strokes. Since Update is only called when the KeyEventFilter is fired, there isn’t an elegant way to hand my own one-shot code. For now I’m hacking my way around it with a timer to ensure at least so much time goes by between keypresses, but it’s definitely not optimal.
It would be nice if A) KeyEventFilter was only sent once at the beginning of each press or B) a certain code was sent on release of the key. But neither of these are present. So for now I will have to either keep using the delta timing method I’m using now, or have an Update that needs run from Main each iteration so that the KeyEventFilter can be one-shotted.
February 12, 2018 at 2:01 am #13615Did you try to check event.Type property inside your filter method?
If Not event.Type=EventType.KeyReleased Return
February 12, 2018 at 7:31 am #13616Thank you again. I think I’m finally getting a handle on things
Here is the current state of things. I’ll start on joystick stuff soon then look for ways to clean things up a bit
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179Namespace CheatSequence#Import "<std>"#Import "<mojo>"Using std..Using mojo..' ------------------------------------------------------------------------------------------------------------------' SIMPLE CHEAT CLASS' ------------------------------------------------------------------------------------------------------------------' We will define some constantsEnum StrokeTypeKey = 1Joy = 2EndClass CheatSequencePublicField numStrokes:Int ' Number of strokes in this sequenceField strokeCounter:Int ' Current stroke in the sequenceField strokes:Map<Int,SequenceStroke> ' Map of strokes in the sequenceField timeOut:Int ' millisecs timeoutField lastStrokeTimestamp:Int ' timestamp of last stroke inputField OnSuccess:Void() ' Signal when sequence is successful (first class function)' Create a new cheat sequence' Default to a 1 second timeoutMethod New(thisTimeOut:Int=1000)numStrokes=0strokeCounter=0timeOut=thisTimeOutstrokes=New Map<Int,SequenceStroke>Enabled=True ' enable by defaultEnd' Create and populate sequence all at onceMethod New(thisTimeout:Int,strokeType:Int,strokeValues:Int[])Self.New(thisTimeout)AddStrokes(strokeType,strokeValues)End Method' Add a stroke to the sequenceMethod AddStroke(strokeType:Int,strokeValue:Int)strokes.Add(numStrokes,New SequenceStroke(strokeType,strokeValue))numStrokes+= 1End Method' Add several strokes of the same stroke type to the sequenceMethod AddStrokes( strokeType:Int,strokeValues:Int[] )For Local strokeValue:=Eachin strokeValuesAddStroke(strokeType,strokeValue)NextEnd' Enable/Disable the cheatSequenceProperty Enabled:Bool()Return _enabledSetter( value:Bool )If value=_enabled Return_enabled=valueIf _enabledApp.KeyEventFilter+=ProcessKeys ' subscribe to filterElseApp.KeyEventFilter-=ProcessKeys ' unsubscribe from filterEndifEndPrivateField _enabled:Bool' Process key strokes as they are fired by the App.KeyEventFilterMethod ProcessKeys( event:KeyEvent )' A key hit was signaled. Lets update the sequenceUpdateSequence( event )' and check does a user finish the sequenceIf Success()OnSuccess() ' and emit event if successEndifEnd' The sequence gets updated each keypressMethod UpdateSequence(thisEvent:KeyEvent)If Millisecs()-lastStrokeTimestamp > timeOut And strokeCounter>0'We timed out, lets restartSelf.strokeCounter=0lastStrokeTimestamp=Millisecs()Else' We didn't timeout, so lets update the sequenceIf thisEvent.Type = EventType.KeyDownIf strokes.Get(strokeCounter).strokeType=StrokeType.Key And strokes.Get(strokeCounter).strokeValue=thisEvent.KeySelf.strokeCounter+=1Self.lastStrokeTimestamp=Millisecs()ElseSelf.strokeCounter=0EndifEndifEndifEnd Method' Check to see if the cheat is successfulMethod Success:Bool()If (Self.strokeCounter=Self.numStrokes)Self.strokeCounter=0Return TrueEndifReturn FalseEndEnd' Store a cheat stroke. This includes the kind of stroke (keyboard, joystick, etc..)' the value of the stroke (whick key or button)Class SequenceStrokeField strokeType:IntField strokeValue:IntMethod New(thisStrokeType:Int,thisStrokeValue:Int)strokeType=thisStrokeTypestrokeValue=thisStrokeValueEndEnd' ------------------------------------------------------------------------------------------------------------------' END SIMPLE CHEAT CLASS' ------------------------------------------------------------------------------------------------------------------Class MyWindow Extends WindowField contraCheat:CheatSequenceField lives:IntMethod New()Super.New( "Cheat Test!",640,480,WindowFlags.Fullscreen )lives=3' Create our UUDDLRLRBA<Enter> cheat sequencecontraCheat = New CheatSequence(1000,StrokeType.Key,New Int[](Key.Up,Key.Up,Key.Down,Key.Down,Key.Left,Key.Right,Key.Left,Key.Right,Key.B,Key.A,Key.Enter))' Increase lives by 30 if the cheat sequence is successfully enteredcontraCheat.OnSuccess=Lambda()lives+=30EndEndMethod OnRender( canvas:Canvas ) Override' Debug display of cheat sequenceLocal delta:=Millisecs() - contraCheat.lastStrokeTimestampcanvas.Color=Color.Goldcanvas.DrawText("Lives: " + lives,App.ActiveWindow.Width/2 ,App.ActiveWindow.Height/2)canvas.Color=Color.Whitecanvas.DrawText("Try the cheat! UUDDLRLRBA<Enter>",4,App.ActiveWindow.Height-95)canvas.DrawText("Timer: " + delta,4,App.ActiveWindow.Height-55)canvas.DrawText("numStokes: " + contraCheat.numStrokes,4,App.ActiveWindow.Height-40)canvas.DrawText("strokeCounter : " + contraCheat.strokeCounter,4,App.ActiveWindow.Height-25)App.RequestRender()EndMethod OnKeyEvent( event:KeyEvent ) Overrideselect event.TypeCase EventType.KeyDown' Exit the appIf ( event.Key=Key.Escape ) Then App.Terminate()EndEndEndFunction Main()New AppInstanceNew MyWindowApp.Run()End -
AuthorPosts
You must be logged in to reply to this topic.